Application Load Balancer with Target Group and HTTPS Listener
Scenario
Your Auto Scaling Group runs multiple EC2 instances, but there is no single DNS name for the application and no way to distribute traffic evenly across instances. You need an Application Load Balancer in front of the ASG to route HTTP requests and return a 301 redirect on port 80 to HTTPS on port 443.
- Create an aws_lb (ALB) in two public subnets with access logging enabled.
- Create an aws_lb_target_group with an HTTP health check on /health.
- Create an HTTP listener on port 80 that issues a permanent redirect to HTTPS.
- Create an HTTPS listener on port 443 (you may use a placeholder ACM cert ARN) that forwards to the target group.
- Attach the ASG to the target group using aws_autoscaling_attachment.
- Output the ALB DNS name.
Additional Context
The deregistration_delay on the target group controls how long the ALB waits before removing a deregistering target. Setting it low (e.g., 30 seconds) speeds up rolling deployments. The default is 300 seconds (5 minutes).
For the HTTPS listener, a real ACM certificate ARN is required by AWS — this lab uses a variable so you can substitute a real certificate ARN provisioned via AWS Certificate Manager. For local testing, you can simplify by using an HTTP-only listener (port 80 forwarding directly to the target group).
versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
variable "vpc_id" { type = string }
variable "public_subnets" { type = list(string); description = "At least 2 public subnets in different AZs." }
variable "asg_name" { type = string; description = "Name of the Auto Scaling Group to attach." }
variable "certificate_arn"{ type = string; description = "ACM certificate ARN for HTTPS listener." }
variable "environment" { type = string; default = "prod" }main.tf
# main.tf
provider "aws" {
region = var.aws_region
}
# Security Group for the ALB — allows inbound HTTP and HTTPS from internet.
resource "aws_security_group" "alb" {
name = "alb-sg-${var.environment}"
vpc_id = var.vpc_id
ingress { from_port = 80; to_port = 80; protocol = "tcp"; cidr_blocks = ["0.0.0.0/0"] }
ingress { from_port = 443; to_port = 443; protocol = "tcp"; cidr_blocks = ["0.0.0.0/0"] }
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = ["0.0.0.0/0"] }
tags = { Name = "alb-sg", Environment = var.environment }
}
# S3 bucket for ALB access logs (optional but recommended in production).
resource "aws_s3_bucket" "alb_logs" {
bucket = "alb-access-logs-${var.environment}-${data.aws_caller_identity.current.account_id}"
force_destroy = true # allow bucket destruction while it has log objects
tags = { ManagedBy = "terraform", Environment = var.environment }
}
data "aws_caller_identity" "current" {}
data "aws_elb_service_account" "main" {}
resource "aws_s3_bucket_policy" "alb_logs" {
bucket = aws_s3_bucket.alb_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { AWS = data.aws_elb_service_account.main.arn }
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.alb_logs.arn}/alb/AWSLogs/${data.aws_caller_identity.current.account_id}/*"
}]
})
}
# Application Load Balancer.
resource "aws_lb" "web" {
name = "web-alb-${var.environment}"
load_balancer_type = "application"
internal = false # internet-facing
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnets # span at least 2 AZs
access_logs {
bucket = aws_s3_bucket.alb_logs.id
prefix = "alb"
enabled = true
}
tags = { Name = "web-alb", Environment = var.environment, ManagedBy = "terraform" }
}
# Target Group — the set of EC2 instances that the ALB routes to.
resource "aws_lb_target_group" "web" {
name = "web-tg-${var.environment}"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "instance"
# Faster deregistration for rolling deployments.
deregistration_delay = 30
health_check {
path = "/health"
port = "traffic-port"
protocol = "HTTP"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 15
timeout = 5
matcher = "200"
}
tags = { Environment = var.environment, ManagedBy = "terraform" }
}
# HTTP listener — redirects all HTTP to HTTPS (HTTP 301).
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.web.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
# HTTPS listener — forwards traffic to the target group.
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.web.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" # TLS 1.3 preferred
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
# Attach the Auto Scaling Group to the Target Group.
# The ASG will automatically register/deregister instances as they scale.
resource "aws_autoscaling_attachment" "web" {
autoscaling_group_name = var.asg_name
lb_target_group_arn = aws_lb_target_group.web.arn
}outputs.tf
output "alb_dns_name" { value = aws_lb.web.dns_name }
output "alb_arn" { value = aws_lb.web.arn }
output "target_group_arn" { value = aws_lb_target_group.web.arn }Workflow Commands
terraform init
terraform apply -var-file="prod.tfvars"
# Test the ALB (HTTP should redirect to HTTPS)
curl -L http://$(terraform output -raw alb_dns_name)/health
# View target group health
aws elbv2 describe-target-health \
--target-group-arn $(terraform output -raw target_group_arn)
terraform destroy -var-file="prod.tfvars"Apply complete! Resources: 7 added, 0 changed, 0 destroyed. Outputs: alb_arn = "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/web-alb-prod/..." alb_dns_name = "web-alb-prod-1234567890.us-east-1.elb.amazonaws.com" target_group_arn = "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/web-tg-prod/..."

