Exploring Terraform on AWS: Setting Up ALB Security Groups
Recently, I was exploring Terraform on AWS and learned how to deploy an Application Load Balancer (ALB). In this post, I explain how I created security groups and used Terraform to get the latest Amazon Linux 2 AMI.
This post is for anyone who is learning Infrastructure as Code (IaC) and wants to understand AWS networking better.
What I Set Up
In this setup, I created:
- Three security groups using the Terraform AWS Security Group module:
- One for a public bastion host (for SSH access)
- One for private EC2 instances
- One for a public load balancer
- A data block to get the latest Amazon Linux 2 AMI automatically
If you have not yet created a VPC with Terraform, you can check that part first before applying these modules.
1) Public Bastion Host Security Group
module "public_bastion_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "5.2.0"
name = "public-bastion-sg"
description = "Security Group with SSH port open for everybody"
vpc_id = module.vpc.vpc_id
ingress_rules = ["ssh-tcp"]
ingress_cidr_blocks = ["0.0.0.0/0"]
egress_rules = ["all-all"]
tags = local.common_tags
}
Explanation:
- The
sourceandversiontell Terraform to use a ready-made module from the Terraform Registry and keep it stable with a specific version. - The
nameanddescriptionfields help you identify this group in the AWS console. - The
vpc_idconnects the security group to your main VPC. - The
ingress_rulesallow incoming SSH traffic (port 22) from anywhere. This is useful for connecting to your EC2 instance through a bastion host. - The
egress_rulesallow all outbound traffic so the instance can reach the internet (for updates or downloads). - The
tagshelp with organization and make it easier to track resources in AWS.
This security group is used for your bastion host, which you can connect to for managing private instances.
2) Private EC2 Instances Security Group
module "private_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "5.2.0"
name = "private-sg"
description = "Security Group with HTTP and SSH open inside the VPC"
vpc_id = module.vpc.vpc_id
ingress_rules = ["ssh-tcp", "http-80-tcp"]
ingress_cidr_blocks = [module.vpc.vpc_cidr_block]
egress_rules = ["all-all"]
tags = local.common_tags
}
Explanation:
- This group allows SSH (port 22) and HTTP (port 80) connections, but only from within the VPC range.
- The
ingress_cidr_blocksuse the VPC CIDR block, which means only servers inside the same network can connect. - This setup improves security because it blocks all public access from the internet.
- The
egress_rulesagain allow all outbound traffic so your instance can connect to external services if needed. - This type of group is good for application servers or private services that are not open to the public.
3) Public Load Balancer Security Group
module "loadbalancer_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "5.2.0"
name = "loadbalancer-sg"
description = "Security Group with HTTP open for the Internet"
vpc_id = module.vpc.vpc_id
ingress_rules = ["http-80-tcp"]
ingress_cidr_blocks = ["0.0.0.0/0"]
egress_rules = ["all-all"]
tags = local.common_tags
ingress_with_cidr_blocks = [
{
from_port = 81
to_port = 81
protocol = 6
description = "Allow Port 81 from Internet"
cidr_blocks = "0.0.0.0/0"
},
]
}
Explanation:
- The load balancer needs to accept traffic from the internet, so
ingress_cidr_blocksis set to0.0.0.0/0, which means open to all IPs. - The rule
http-80-tcpallows normal HTTP traffic on port 80 for websites. - The extra rule with port 81 shows how you can allow custom ports if your app uses them.
- The
egress_rulesallow the load balancer to send responses back to users or forward traffic to backend servers. - This group is essential for public-facing applications such as web servers or APIs behind an ALB.
4) Security Group Outputs
output "public_bastion_sg_group_id" {
description = "The ID of the security group"
value = module.public_bastion_sg.security_group_id
}
Explanation:
- The
outputblock lets Terraform print important values after applying the configuration. - For example,
security_group_idcan be reused in other Terraform files or modules (for EC2 instance connections, ALB targets, etc.). - Using outputs keeps your setup clean and helps you easily connect different modules together without hardcoding values.
5) Getting the Latest Amazon Linux 2 AMI
data "aws_ami" "amzlinux2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-gp2"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
}
Explanation:
- This
datablock helps Terraform find the newest Amazon Linux 2 AMI automatically instead of entering a static AMI ID. - The filters tell Terraform what type of image to look for — here it searches only for 64-bit (x86_64) HVM images with EBS storage.
- The
most_recent = trueoption makes sure Terraform always picks the latest version available from Amazon. - This saves time and keeps your setup up to date whenever you deploy new instances.
Wrap-Up
This setup helps you manage AWS security groups easily with Terraform and keeps your infrastructure consistent.
You learned how to:
- Create security groups for bastion hosts, private EC2s, and load balancers
- Output key IDs for reuse
- Automatically fetch the latest Amazon Linux 2 AMI
In the next step, I will deploy EC2 instances, attach these security groups, and connect them to the ALB to handle incoming traffic.