AWS Application Load Balancer (ALB) Setup Reference Guide

Table of contents

  1. Introduction
  2. Prerequisites
  3. Step 1 — Configure Security Groups
    1. ALB Security Group
    2. Target Security Group
    3. Terraform
  4. Step 2 — Create the ALB
    1. AWS Console
    2. AWS CLI
    3. Terraform
  5. Step 3 — Create Target Groups
    1. AWS Console
    2. AWS CLI
    3. Terraform
  6. Step 4 — Configure Health Checks
    1. Health Check Best Practices
  7. Step 5 — Create Listeners
    1. HTTP Listener (Port 80) — Redirect to HTTPS
    2. HTTPS Listener (Port 443) — Forward to Target Group
    3. Terraform
  8. Step 6 — Configure Listener Rules (Routing)
    1. Path-Based Routing
    2. Host-Based Routing
    3. Terraform — Multiple Rules
    4. Routing Conditions Summary
  9. Step 7 — SSL/TLS Certificate Setup
    1. Request a Certificate via ACM
    2. AWS CLI
    3. Attach Additional Certificates (SNI)
    4. SSL Security Policy Selection
  10. Step 8 — Enable Access Logs
    1. Enable via Console
    2. AWS CLI
    3. S3 Bucket Policy
    4. Terraform
  11. Step 9 — Enable WAF Integration
    1. AWS CLI
    2. Terraform
    3. Recommended WAF Managed Rules
  12. Step 10 — Configure Stickiness (Optional)
    1. Duration-Based (ALB-Generated Cookie)
    2. Application-Based (Custom Cookie)
  13. Step 11 — Configure Monitoring with CloudWatch
    1. Key ALB CloudWatch Metrics
    2. Create a CloudWatch Alarm (AWS CLI)
  14. Step 12 — DNS Configuration
    1. Route 53 Alias Record (Recommended)
    2. Terraform
  15. Step 13 — Integration with Auto Scaling Group
    1. AWS CLI
    2. Terraform
  16. Complete Terraform Example
  17. Troubleshooting Checklist
  18. References

Introduction

This guide provides a detailed, step-by-step reference for setting up an AWS Application Load Balancer (ALB). It covers the full lifecycle from prerequisites through production-ready configuration, including HTTPS, health checks, routing rules, WAF integration, and monitoring.

An ALB operates at layer 7 (HTTP/HTTPS) and is best suited for web applications, microservices, and container-based architectures. It supports advanced routing, SSL/TLS termination, WebSockets, HTTP/2, and integration with AWS WAF, Cognito, and ECS.


Prerequisites

Before creating an ALB, ensure the following are in place:

PrerequisiteDescription
VPCA VPC with at least two public subnets in different Availability Zones.
Security GroupsA security group for the ALB allowing inbound traffic on ports 80 (HTTP) and/or 443 (HTTPS).
Target Instances/ContainersEC2 instances, ECS tasks, IP addresses, or Lambda functions to receive traffic.
SSL/TLS CertificateAn ACM certificate (or uploaded certificate) if configuring HTTPS.
IAM PermissionsSufficient IAM permissions to create and manage ELB, EC2, ACM, and S3 resources.
DNS (Optional)A Route 53 hosted zone or external DNS for custom domain mapping.

Step 1 — Configure Security Groups

The ALB requires a security group at creation time. Create security groups for both the ALB and its targets first.

ALB Security Group

RuleTypeProtocolPortSource
InboundHTTPTCP800.0.0.0/0 (or restricted CIDR)
InboundHTTPSTCP4430.0.0.0/0 (or restricted CIDR)
OutboundAll trafficAllAllTarget security group

Target Security Group

RuleTypeProtocolPortSource
InboundApplicationTCPApplication port (e.g., 8080)ALB security group ID

Targets should only accept traffic from the ALB security group, not directly from the internet.

Terraform

resource "aws_security_group" "alb" {
  name   = "alb-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    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"]
  }
}

resource "aws_security_group" "targets" {
  name   = "targets-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }
}

Step 2 — Create the ALB

AWS Console

  1. Open the EC2 ConsoleLoad BalancersCreate Load Balancer.
  2. Select Application Load Balancer.
  3. Configure basic settings:
    • Name: A descriptive name (e.g., my-app-alb).
    • Scheme: internet-facing (public) or internal (private).
    • IP address type: ipv4 or dualstack (IPv4 + IPv6).
  4. Under Network mapping:
    • Select the VPC.
    • Select at least two subnets in different AZs.
  5. Assign the security group created in Step 1.

AWS CLI

aws elbv2 create-load-balancer \
  --name my-app-alb \
  --subnets subnet-0abc1234 subnet-0def5678 \
  --security-groups sg-0abc1234 \
  --scheme internet-facing \
  --type application \
  --ip-address-type ipv4

Terraform

resource "aws_lb" "app" {
  name               = "my-app-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]

  enable_deletion_protection = true

  tags = {
    Environment = "production"
  }
}

Step 3 — Create Target Groups

Target groups define where the ALB routes traffic and how it checks target health.

AWS Console

  1. EC2 ConsoleTarget GroupsCreate target group.
  2. Choose target type: Instances, IP addresses, Lambda function, or ALB.
  3. Configure:
    • Name: e.g., my-app-tg.
    • Protocol: HTTP or HTTPS.
    • Port: Application port (e.g., 8080).
    • VPC: Same VPC as the ALB.
    • Protocol version: HTTP/1.1 or HTTP/2 (or gRPC).
  4. Configure health checks (see Step 4).
  5. Register targets (instances, IPs, or Lambda functions).

AWS CLI

aws elbv2 create-target-group \
  --name my-app-tg \
  --protocol HTTP \
  --port 8080 \
  --vpc-id vpc-0abc1234 \
  --target-type instance \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --healthy-threshold-count 3 \
  --unhealthy-threshold-count 2

aws elbv2 register-targets \
  --target-group-arn arn:aws:elasticloadbalancing:region:account:targetgroup/my-app-tg/1234567890 \
  --targets Id=i-0abc1234 Id=i-0def5678

Terraform

resource "aws_lb_target_group" "app" {
  name        = "my-app-tg"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "instance"

  health_check {
    enabled             = true
    path                = "/health"
    port                = "traffic-port"
    protocol            = "HTTP"
    healthy_threshold   = 3
    unhealthy_threshold = 2
    interval            = 30
    timeout             = 5
    matcher             = "200"
  }

  deregistration_delay = 300
}

Step 4 — Configure Health Checks

Health checks determine whether targets are healthy and eligible to receive traffic.

SettingRecommended ValueDescription
ProtocolHTTPProtocol used for health checks.
Path/healthEndpoint that returns health status.
Porttraffic-portSame port as the target receives traffic on.
Healthy threshold3Consecutive successes to mark healthy.
Unhealthy threshold2Consecutive failures to mark unhealthy.
Interval30 secondsTime between health checks.
Timeout5 secondsTime to wait for a response.
Success codes200 (or 200-299)HTTP status codes indicating health.

Health Check Best Practices

  • The health check endpoint should be lightweight — avoid database queries or heavy computation.
  • Return a 200 OK with a simple JSON body (e.g., {"status": "UP"}).
  • Set the unhealthy threshold lower than the healthy threshold for faster failure detection.
  • If using a grace period (with ASG), ensure it exceeds the application startup time.
  • If all targets are unhealthy, the ALB will route traffic to all targets (fail-open behavior).

Step 5 — Create Listeners

Listeners check for connection requests from clients using the protocol and port you configure.

HTTP Listener (Port 80) — Redirect to HTTPS

aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:region:account:loadbalancer/app/my-app-alb/1234567890 \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=redirect,RedirectConfig="{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}"

HTTPS Listener (Port 443) — Forward to Target Group

aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:region:account:loadbalancer/app/my-app-alb/1234567890 \
  --protocol HTTPS \
  --port 443 \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --certificates CertificateArn=arn:aws:acm:region:account:certificate/cert-id \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:region:account:targetgroup/my-app-tg/1234567890

Terraform

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.app.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.app.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate.app.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

Step 6 — Configure Listener Rules (Routing)

Listener rules define how the ALB routes requests beyond the default action. Rules are evaluated in priority order (lowest number first).

Path-Based Routing

Route requests to different target groups based on the URL path.

aws elbv2 create-rule \
  --listener-arn arn:aws:elasticloadbalancing:region:account:listener/app/my-app-alb/1234567890/listener-id \
  --priority 10 \
  --conditions Field=path-pattern,Values='/api/*' \
  --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:region:account:targetgroup/api-tg/1234567890

Host-Based Routing

Route requests based on the Host header (useful for multi-tenant or multi-domain setups).

aws elbv2 create-rule \
  --listener-arn arn:aws:elasticloadbalancing:region:account:listener/app/my-app-alb/1234567890/listener-id \
  --priority 20 \
  --conditions Field=host-header,Values='api.example.com' \
  --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:region:account:targetgroup/api-tg/1234567890

Terraform — Multiple Rules

resource "aws_lb_listener_rule" "api" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 10

  condition {
    path_pattern {
      values = ["/api/*"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.api.arn
  }
}

resource "aws_lb_listener_rule" "admin" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 20

  condition {
    host_header {
      values = ["admin.example.com"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.admin.arn
  }
}

Routing Conditions Summary

ConditionFieldExample
Path patternpath-pattern/api/*, /images/*
Host headerhost-headerapi.example.com
HTTP headerhttp-headerX-Custom-Header: value
HTTP methodhttp-request-methodPOST, GET
Query stringquery-string?platform=mobile
Source IPsource-ip10.0.0.0/8

Step 7 — SSL/TLS Certificate Setup

Request a Certificate via ACM

  1. Open ACM ConsoleRequest a certificate.
  2. Choose Public certificate.
  3. Enter domain names (e.g., example.com, *.example.com).
  4. Select DNS validation (recommended).
  5. Create the CNAME record in Route 53 or your DNS provider.
  6. Wait for status to change to Issued.

AWS CLI

aws acm request-certificate \
  --domain-name example.com \
  --subject-alternative-names "*.example.com" \
  --validation-method DNS

Attach Additional Certificates (SNI)

For serving multiple domains on a single ALB, add additional certificates to the HTTPS listener:

aws elbv2 add-listener-certificates \
  --listener-arn arn:aws:elasticloadbalancing:region:account:listener/app/my-app-alb/1234567890/listener-id \
  --certificates CertificateArn=arn:aws:acm:region:account:certificate/additional-cert-id

ALB uses Server Name Indication (SNI) to select the correct certificate based on the hostname in the TLS handshake.

SSL Security Policy Selection

PolicyTLS VersionsUse Case
ELBSecurityPolicy-TLS13-1-2-2021-06TLS 1.2 + 1.3Recommended for most workloads.
ELBSecurityPolicy-TLS13-1-3-2021-06TLS 1.3 onlyMaximum security, modern clients only.
ELBSecurityPolicy-2016-08TLS 1.0 + 1.1 + 1.2Legacy compatibility (not recommended).

Step 8 — Enable Access Logs

Access logs capture detailed information about every request processed by the ALB.

Enable via Console

  1. EC2 ConsoleLoad Balancers → Select ALB → AttributesEdit.
  2. Enable Access logs.
  3. Specify the S3 bucket and optional prefix.

AWS CLI

aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn arn:aws:elasticloadbalancing:region:account:loadbalancer/app/my-app-alb/1234567890 \
  --attributes Key=access_logs.s3.enabled,Value=true \
               Key=access_logs.s3.bucket,Value=my-alb-logs-bucket \
               Key=access_logs.s3.prefix,Value=alb-logs

S3 Bucket Policy

The S3 bucket must allow the ELB service to write logs. Replace REGION and ELB_ACCOUNT_ID with the ELB account ID for your region.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ELB_ACCOUNT_ID:root"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-alb-logs-bucket/alb-logs/AWSLogs/ACCOUNT_ID/*"
    }
  ]
}

Terraform

resource "aws_lb" "app" {
  # ... other configuration ...

  access_logs {
    bucket  = aws_s3_bucket.alb_logs.id
    prefix  = "alb-logs"
    enabled = true
  }
}

Step 9 — Enable WAF Integration

Attach an AWS WAF Web ACL to the ALB to protect against common web exploits.

AWS CLI

aws wafv2 associate-web-acl \
  --web-acl-arn arn:aws:wafv2:region:account:regional/webacl/my-web-acl/acl-id \
  --resource-arn arn:aws:elasticloadbalancing:region:account:loadbalancer/app/my-app-alb/1234567890

Terraform

resource "aws_wafv2_web_acl_association" "alb" {
  resource_arn = aws_lb.app.arn
  web_acl_arn  = aws_wafv2_web_acl.main.arn
}
Rule GroupProtection
AWSManagedRulesCommonRuleSetGeneral web exploits (XSS, path traversal, etc.)
AWSManagedRulesSQLiRuleSetSQL injection attacks.
AWSManagedRulesKnownBadInputsRuleSetKnown malicious inputs (Log4j, etc.)
AWSManagedRulesAmazonIpReputationListBlocks IPs with poor reputation.
AWSManagedRulesBotControlRuleSetBot detection and mitigation.

Step 10 — Configure Stickiness (Optional)

Enable session stickiness if your application requires requests from the same client to reach the same target.

aws elbv2 modify-target-group-attributes \
  --target-group-arn arn:aws:elasticloadbalancing:region:account:targetgroup/my-app-tg/1234567890 \
  --attributes Key=stickiness.enabled,Value=true \
               Key=stickiness.type,Value=lb_cookie \
               Key=stickiness.lb_cookie.duration_seconds,Value=86400
aws elbv2 modify-target-group-attributes \
  --target-group-arn arn:aws:elasticloadbalancing:region:account:targetgroup/my-app-tg/1234567890 \
  --attributes Key=stickiness.enabled,Value=true \
               Key=stickiness.type,Value=app_cookie \
               Key=stickiness.app_cookie.cookie_name,Value=MY_APP_COOKIE \
               Key=stickiness.app_cookie.duration_seconds,Value=86400

Stickiness may cause uneven load distribution. Use only when required by the application (e.g., session state stored in memory).


Step 11 — Configure Monitoring with CloudWatch

Key ALB CloudWatch Metrics

MetricDescriptionAlarm Threshold (Example)
RequestCountTotal number of requests processed.Baseline + 50%
TargetResponseTimeAverage time for targets to respond.> 2 seconds
HTTPCode_ELB_5XX_Count5xx errors generated by the ALB.> 0
HTTPCode_Target_5XX_Count5xx errors returned by targets.> 10/min
HealthyHostCountNumber of healthy targets.< desired count
UnHealthyHostCountNumber of unhealthy targets.> 0
ActiveConnectionCountTotal active concurrent connections.Capacity planning
RejectedConnectionCountConnections rejected (max connections reached).> 0

Create a CloudWatch Alarm (AWS CLI)

aws cloudwatch put-metric-alarm \
  --alarm-name alb-5xx-errors \
  --metric-name HTTPCode_ELB_5XX_Count \
  --namespace AWS/ApplicationELB \
  --statistic Sum \
  --period 300 \
  --threshold 10 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 1 \
  --dimensions Name=LoadBalancer,Value=app/my-app-alb/1234567890 \
  --alarm-actions arn:aws:sns:region:account:my-alerts-topic

Step 12 — DNS Configuration

Point your custom domain to the ALB using Route 53 or an external DNS provider.

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890 \
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "app.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z35SXDOTRQ7X7K",
          "DNSName": "my-app-alb-1234567890.us-east-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }]
  }'

Terraform

resource "aws_route53_record" "app" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"

  alias {
    name                   = aws_lb.app.dns_name
    zone_id                = aws_lb.app.zone_id
    evaluate_target_health = true
  }
}

Use Alias records (not CNAME) for ALBs in Route 53 — they are free and support zone apex domains.


Step 13 — Integration with Auto Scaling Group

Attach the ALB target group to an ASG so new instances are automatically registered.

AWS CLI

aws autoscaling attach-load-balancer-target-groups \
  --auto-scaling-group-name my-asg \
  --target-group-arns arn:aws:elasticloadbalancing:region:account:targetgroup/my-app-tg/1234567890

Terraform

resource "aws_autoscaling_group" "app" {
  name                = "my-asg"
  min_size            = 2
  max_size            = 10
  desired_capacity    = 2
  vpc_zone_identifier = [aws_subnet.private_a.id, aws_subnet.private_b.id]
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"

  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
}

Set health_check_type = "ELB" on the ASG so that unhealthy targets (as determined by ALB health checks) are automatically replaced.


Complete Terraform Example

A minimal but production-ready ALB setup:

# ALB
resource "aws_lb" "app" {
  name               = "my-app-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]

  enable_deletion_protection = true

  access_logs {
    bucket  = aws_s3_bucket.alb_logs.id
    prefix  = "alb-logs"
    enabled = true
  }
}

# Target Group
resource "aws_lb_target_group" "app" {
  name        = "my-app-tg"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "instance"

  health_check {
    path                = "/health"
    protocol            = "HTTP"
    healthy_threshold   = 3
    unhealthy_threshold = 2
    interval            = 30
    timeout             = 5
    matcher             = "200"
  }

  deregistration_delay = 300
}

# HTTP → HTTPS Redirect
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.app.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

# HTTPS Listener
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.app.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate.app.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# DNS
resource "aws_route53_record" "app" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"

  alias {
    name                   = aws_lb.app.dns_name
    zone_id                = aws_lb.app.zone_id
    evaluate_target_health = true
  }
}

Troubleshooting Checklist

IssuePossible CauseResolution
502 Bad GatewayTarget is not responding or returning malformed responses.Check target health, security groups, and application logs.
503 Service UnavailableNo healthy targets in the target group.Verify health check configuration and target status.
504 Gateway TimeoutTarget did not respond within the idle timeout.Increase ALB idle timeout or optimize target response time.
Targets stuck in unhealthyHealth check path returns non-200 or times out.Verify the health check path, port, and security group rules.
Uneven traffic distributionStickiness enabled or cross-zone load balancing disabled.Review stickiness settings; ensure cross-zone is enabled.
SSL handshake errorsCertificate mismatch or expired certificate.Verify ACM certificate covers the requested domain.
Connection refusedSecurity group does not allow traffic on the listener port.Update ALB security group inbound rules.

References