AWS Application Load Balancer (ALB): How It Works, Use Cases, and Terraform
When you build a web application or API on AWS and need to distribute traffic across multiple servers, an Application Load Balancer (ALB) is the standard tool. It accepts HTTP and HTTPS requests, applies routing rules based on the content of each request, and forwards traffic to the correct backend. Most production web workloads on AWS have an ALB at the front.
ALBs work at Layer 7, which means they can read HTTP headers, inspect URL paths, and make routing decisions based on request content. This is what separates them from a basic network load balancer that only looks at IP and port. You can run an API, a frontend, and an admin panel behind a single ALB and it routes each request to the right place using listener rules.
This page explains how an ALB works, walks through the key concepts (listeners, rules, target groups, health checks), covers security setup and TLS termination, and includes a ready-to-use Terraform example. For a hands-on step-by-step walkthrough, see the HTTP Load Balancer Setup guide.
Simple explanation
Think of an ALB as a traffic router sitting at the entrance to your application. Every HTTP request that arrives at your domain passes through the ALB first. The ALB looks at the request: what URL, what domain, what headers. Then it applies your routing rules to decide which backend should handle it.
If your app is a single service running on three EC2 instances, the ALB simply distributes requests across all three and skips any that are unhealthy. If your app is a collection of microservices, the ALB routes each request to the right service based on the URL path or subdomain, all through a single entry point and a single DNS name.
Analogy: the hotel front desk. Imagine a hotel where guests all walk in through the same front door. Some want the restaurant, some want the gym, some want to check in. The front desk (your ALB listener) greets every guest, figures out what they need, and directs them to the right place. The restaurant is one target group, the gym is another, and the check-in counter is the default. The front desk also calls ahead to make sure each department is open before sending anyone there. That call-ahead check is the health check.
The key difference from a basic load balancer: A Network Load Balancer routes by IP and port only. An ALB reads the HTTP request before deciding where to send it. This makes it possible to route /api/* to one service and /web/* to another through a single entry point, without running separate load balancers for each service.
When to use an ALB
Use an ALB when your workload communicates over HTTP or HTTPS. Specific cases where ALB is the right choice:
- Web applications: any app users access through a browser over HTTPS
- REST APIs: HTTP APIs distributed across multiple backend instances
- Microservices: multiple services sharing a single domain entry point, routed by path or subdomain
- Path-based routing: send
/api/*to one target group and/app/*to another - Host-based routing: serve
api.example.comandadmin.example.comfrom one ALB - TLS termination: terminate HTTPS at the ALB using a free ACM certificate; backends receive plain HTTP inside the VPC
- Container workloads: ECS services with dynamic port mapping, or Kubernetes services via an ingress controller
- Lambda backends: route specific paths to a Lambda function with no servers required
- Auto Scaling: pair with an Auto Scaling group so new instances automatically register and receive traffic
Quick decision rule: If your app speaks HTTP or HTTPS, use ALB. If it speaks raw TCP, UDP, or a custom binary protocol, use NLB instead. When in doubt, ALB is the right starting point for anything web-facing.
When not to use an ALB
ALBs only understand HTTP and HTTPS. If your workload does not use HTTP, use a Network Load Balancer (NLB) instead. Specific cases where NLB is a better fit:
- Non-HTTP protocols: TCP, UDP, TLS passthrough, database connections, MQTT, or any custom binary protocol
- Static IP requirement: ALBs use DNS and do not have fixed IP addresses. If a downstream firewall requires a fixed IP for allowlisting, use NLB (which supports Elastic IPs) or place a Global Accelerator in front of the ALB
- Ultra-low latency TCP/UDP: NLB operates at Layer 4 with near-zero added overhead; ALB adds a small processing cost for HTTP inspection
- Preserving client source IP natively: NLB preserves the original source IP transparently; ALB passes it via the
X-Forwarded-Forheader, which requires your application to read it deliberately
See the Internal Load Balancers guide for a full comparison of internal ALB vs internal NLB for service-to-service traffic.
How an AWS ALB works
Here is the full request flow from a client making an HTTPS request to a backend instance sending a response:
- Client makes a request. A browser sends an HTTPS request to
app.example.com. DNS resolves this domain to the ALB’s DNS name via a Route 53 alias record (see DNS with Route 53). - ALB receives the connection. The request hits the ALB on port 443. The ALB terminates the TLS connection using the ACM certificate attached to the listener. Your backend instances never see the raw TLS handshake.
- Listener identifies the request. The HTTPS listener on port 443 receives the decrypted HTTP request and passes it through the rule evaluation engine.
- Rules are evaluated in priority order. The listener checks each rule from lowest to highest priority number. Rules can match on path pattern, host header, HTTP headers, query string, HTTP method, or source IP. The first matching rule wins.
- A target group is selected. The matching rule’s action forwards the request to a specific target group. If no rule matches, the listener’s default rule applies as the catch-all.
- A healthy target is chosen. From within the selected target group, the ALB picks an available, healthy target using round-robin. If a target is failing health checks, it is skipped entirely.
- Request is forwarded. The ALB opens a new connection to the chosen target on the target group’s configured port and sends the HTTP request. The original client IP is passed in the
X-Forwarded-Forheader. - Response is returned. The backend responds, and the ALB proxies the response back to the client over the original TLS connection.
Concrete routing example: A single ALB serves both app.example.com and api.example.com. Rule 1 (priority 10): Host header is api.example.com AND path starts with /v2/, forward to api-v2-tg. Rule 2 (priority 20): Host header is api.example.com, forward to api-v1-tg. Default rule: forward all remaining requests to web-app-tg. Two domains, three services, one ALB.
Core concepts
Listeners
A listener is bound to a specific port and protocol on the ALB. You typically create two: an HTTP listener on port 80 (configured to redirect all traffic to HTTPS) and an HTTPS listener on port 443 (which handles real traffic). An ALB supports up to 50 listeners, though two is the standard setup for almost all workloads.
An HTTPS listener requires an ACM certificate attached to it. The ALB uses that certificate to terminate TLS connections, then forwards plain HTTP to your backends inside the private network. You can attach multiple certificates to one listener; the ALB uses SNI to pick the correct certificate based on the domain the client connects to.
Rules
Rules are the routing logic inside a listener. Each rule has conditions (what to match) and an action (what to do). Available actions:
- Forward: send the request to a specific target group
- Redirect: return an HTTP 301 or 302 response (the standard way to implement HTTP to HTTPS redirect)
- Fixed response: return a static status code and body, useful for maintenance pages or load-balancer-level health endpoints
- Authenticate: integrate with Amazon Cognito or an OIDC provider to authenticate users before forwarding
Rules are evaluated in priority order. The rule with the lowest priority number is evaluated first. Every listener must have a default rule that acts as the catch-all for requests that do not match any other rule.
Target groups
A target group is a named pool of backends. Each target group has a protocol, a port, and a target type:
instance: EC2 instances identified by instance IDip: IP addresses, used for containers with dynamic port mapping, on-premises servers connected via VPN or Direct Connect, or any other IP-addressable resource in the subnetlambda: a Lambda function; the ALB invokes it synchronously with the HTTP request as the event payloadalb: another ALB, used in advanced multi-tier routing scenarios
Health checks
The ALB continuously polls each registered target to verify it can accept traffic. Health check configuration lives at the target group level. Each target group has its own settings:
- Path: the URL to check (e.g.,
/health). Avoid using/in production. A dedicated health endpoint that tests your database or cache connection gives more reliable signal. - Interval: how often to check. Default: 30 seconds.
- Timeout: how long to wait for a response. Default: 5 seconds.
- Healthy threshold: consecutive successes before marking healthy. Default: 5.
- Unhealthy threshold: consecutive failures before removing from rotation. Default: 2.
- Success codes: HTTP codes that count as healthy. Default: 200. Accepts a range (
200-299) or a list (200,301).
What happens when all targets fail health checks? The ALB has nowhere to send matching requests and returns a 502 or 503 to the client. This is one of the most common sources of unexpected outages. See Load Balancer Backend Unhealthy for a full troubleshooting checklist.
Internet-facing vs internal ALB
When you create an ALB, you choose its scheme: internet-facing or internal. This setting cannot be changed after creation.
| Property | Internet-facing | Internal |
|---|---|---|
| IP addresses | Public and private | Private only |
| DNS resolution | Resolves to public IPs from anywhere | Resolves to private IPs within VPC only |
| Appropriate subnets | Public subnets (with internet gateway route) | Private subnets |
| Typical use | End-user HTTP/HTTPS traffic from the internet | Service-to-service traffic inside the VPC |
A critical point that confuses beginners: the ALB must be in public subnets for internet-facing use, but your EC2 instances should be in private subnets. The ALB’s subnets are where it receives incoming connections. Your instances only need to accept traffic from the ALB security group. They never need a public IP or a direct internet route.
For traffic between internal services (for example, your API tier calling your user service), use an internal ALB placed in private subnets. It gets only private IP addresses and a DNS name that resolves only within the VPC. See Internal Load Balancers for a full guide including internal ALB vs internal NLB.
Path-based and host-based routing
Path-based routing
Path-based routing matches the URL path of the incoming request. Rules with lower priority numbers are evaluated first, and the first match wins.
Example: a single ALB fronts three services.
- Priority 10: path is
/api/*, forward toapi-service-tg - Priority 20: path is
/auth/*, forward toauth-service-tg - Default: all other requests, forward to
web-frontend-tg
A request to https://app.example.com/api/users matches priority 10 and goes to the API service. A request to https://app.example.com/ matches no specific rule and falls through to the web frontend. Three services, one domain, one ALB.
Host-based routing
Host-based routing matches the Host HTTP header, which is the domain name the client addressed the request to. This lets a single ALB serve multiple subdomains and route each to a different backend.
Example: all three subdomains point at the same ALB DNS name via Route 53 alias records.
- Priority 10: host is
api.example.com, forward toapi-service-tg - Priority 20: host is
admin.example.com, forward toadmin-service-tg - Default: all other hosts, forward to
web-frontend-tg
Rules can combine both conditions: host is api.example.com AND path starts with /v2/. This gives precise control over routing with a small set of rules. Multiple conditions within a single rule use AND logic. All conditions must match for the rule to fire.
Security and TLS
Security group setup
An ALB is controlled by security groups, and the correct setup requires two groups working together:
- ALB security group: allows inbound TCP 80 and 443 from
0.0.0.0/0(and::/0for IPv6) for an internet-facing ALB. This group faces the internet. - Target security group: allows inbound on the application port sourced from the ALB security group ID, not from a CIDR range. This means only the ALB can reach your instances.
Do not open your target security group to 0.0.0.0/0. If you allow the open internet inbound on your instance security group, anyone can bypass the ALB and hit your instances directly, skipping your WAF rules, routing logic, and rate limiting. Always use the ALB security group ID as the inbound source rule on your targets, never a CIDR range.
HTTPS with ACM
The standard pattern is to terminate TLS at the ALB using a free certificate from AWS Certificate Manager (ACM). You request the certificate, validate domain ownership via DNS, and attach it to the HTTPS listener. ACM renews the certificate automatically before it expires. No calendar reminders needed.
The recommended TLS policy for new ALBs is ELBSecurityPolicy-TLS13-1-2-2021-06. This enforces TLS 1.2 as the minimum and supports TLS 1.3, satisfying most compliance frameworks.
Traffic from the ALB to your backend instances typically travels as plain HTTP inside the private VPC. This is acceptable because the traffic never leaves the VPC. If compliance requires end-to-end encryption, configure the target group protocol as HTTPS and install a certificate on each backend.
WAF integration
Attach an AWS WAF web ACL to an ALB to filter requests before they reach your application. WAF on an ALB provides rate limiting, SQL injection and XSS protection, bot control, and geographic blocking. See Network Security Best Practices for guidance on where WAF fits in a layered security architecture.
Common use cases
EC2 web application. The classic pattern: two or more EC2 instances behind an internet-facing ALB. The ALB distributes requests across healthy instances. Pair with an Auto Scaling group to add and remove instances based on demand. The scaling group registers new instances with the target group automatically when they launch.
ECS / containerized services. ECS can register container tasks directly into an ALB target group using the ip target type. Each task gets its own IP and registers when it starts, deregisters when it stops. Dynamic port mapping means multiple tasks on the same EC2 host can bind different ports without conflict.
Microservices behind one entry point. Multiple independent services, each in its own target group, all fronted by a single ALB. Listener rules route by path or host. Each team deploys their service independently; the ALB routing configuration is the only coordination point between teams.
Lambda HTTP endpoint. An ALB can route specific paths to a Lambda function without needing API Gateway. This is useful when you want a lightweight HTTP interface for a Lambda function, especially for workloads that fit the ALB pricing model better than API Gateway.
Terraform example
This example creates a complete ALB setup: the load balancer, two target groups, an HTTPS listener with an ACM certificate, an HTTP-to-HTTPS redirect listener, and a path-based routing rule.
# The ALB itself — internet-facing, placed in two public subnets across different AZs
resource "aws_lb" "main" {
name = "main-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = [aws_subnet.public_1a.id, aws_subnet.public_1b.id]
# Prevents accidental deletion via Terraform or the console
enable_deletion_protection = true
tags = {
Name = "main-alb"
Environment = "production"
}
}
# Default target group — receives all requests that don't match a specific rule
resource "aws_lb_target_group" "app" {
name = "app-target-group"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
target_type = "instance"
health_check {
enabled = true
path = "/health"
interval = 30
timeout = 5
healthy_threshold = 2 # 2 consecutive successes before marking healthy
unhealthy_threshold = 3
matcher = "200"
}
}
# API target group — receives requests matched by the path-based rule below
resource "aws_lb_target_group" "api" {
name = "api-target-group"
port = 8081
protocol = "HTTP"
vpc_id = aws_vpc.main.id
target_type = "instance"
health_check {
enabled = true
path = "/api/health"
matcher = "200"
}
}
# HTTPS listener — primary listener; handles real traffic on port 443
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" # enforces TLS 1.2+
certificate_arn = aws_acm_certificate_validation.main.certificate_arn
# Default: send everything to the app target group unless a rule matches
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# HTTP listener — immediately redirects all port 80 requests to HTTPS
resource "aws_lb_listener" "http_redirect" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
# Path-based routing rule — requests to /api/* go to the API target group
# Lower priority number = evaluated first; this rule is checked before the default
resource "aws_lb_listener_rule" "api_routing" {
listener_arn = aws_lb_listener.https.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
condition {
path_pattern {
values = ["/api/*"]
}
}
}ALB vs NLB vs CLB
| Feature | ALB | NLB | CLB (legacy) |
|---|---|---|---|
| OSI layer | Layer 7 (application) | Layer 4 (transport) | Layer 4 and 7 (limited) |
| Protocols | HTTP, HTTPS, gRPC | TCP, UDP, TLS | HTTP, HTTPS, TCP, SSL |
| Content-based routing | Yes: path, host, headers, query string | No | Limited |
| Static IP | No (DNS-based) | Yes (Elastic IPs) | No |
| WAF integration | Yes | No | No |
| WebSocket support | Yes | Yes | No |
| Lambda targets | Yes | No | No |
| Preserves source IP | Via X-Forwarded-For header | Yes (natively) | Via X-Forwarded-For |
| Best for | Web apps, REST APIs, microservices | High-throughput TCP/UDP, static IP needed | Legacy only. Do not use for new workloads. |
Decision guide: Choose ALB when your workload uses HTTP or HTTPS and you need content-based routing, WAF integration, or Lambda targets. Choose NLB when you need a static IP, a non-HTTP protocol, or ultra-low-latency TCP/UDP load balancing. Avoid CLB for any new workload. It exists for legacy compatibility only and lacks most features of both ALB and NLB.
Common beginner mistakes
- Creating the ALB in only one Availability Zone. An ALB must span subnets in at least two AZs. If you specify only one subnet, ALB creation fails. A single-AZ load balancer is a single point of failure. One AZ issue takes down your entire application.
- Putting an internet-facing ALB in private subnets. An internet-facing ALB must be in public subnets with a route to an internet gateway. Without that, the ALB cannot receive external traffic even though its scheme is internet-facing. Your EC2 instances can remain in private subnets. See Subnets in AWS for how public and private subnets differ.
- Not allowing traffic from the ALB security group to the target security group. After creating the ALB security group and attaching it to the ALB, you must add an inbound rule to the EC2 instances’ security group that allows the application port from the ALB security group ID. Without this rule, health checks fail and all targets show as unhealthy, even if the application is running fine.
- Opening the target security group to
0.0.0.0/0instead of scoping it to the ALB. If you allow the open internet inbound on your instance security group, anyone can bypass the ALB and hit your instances directly, skipping WAF rules, rate limits, and routing logic. Always reference the ALB security group ID as the source rule, not a CIDR range. - Assuming ALB works for non-HTTP protocols. ALB handles HTTP and HTTPS only. If you try to use it for a TCP database proxy, an MQTT broker, or a custom binary protocol, it will not work. Use NLB for anything that is not HTTP-based.
Summary
- An ALB is a Layer 7 load balancer that routes HTTP/HTTPS requests based on path, host header, query string, and other request content.
- The request flow: client to ALB listener, rule evaluation, target group selection, healthy target, forwarded request.
- Listeners define the port and protocol; rules define routing conditions; target groups define backend resources.
- Target types include EC2 instances, IP addresses (for containers), Lambda functions, and other ALBs.
- Use ALB for web apps, REST APIs, and microservices. Use NLB for non-HTTP protocols, static IP requirements, or ultra-low-latency TCP/UDP.
- An internet-facing ALB belongs in public subnets. Targets can and should be in private subnets. An internal ALB belongs in private subnets for service-to-service routing.
- Always scope the target security group to allow inbound from the ALB security group ID. Never allow the open internet.
- Terminate TLS at the ALB using a free ACM certificate. Traffic from ALB to backends can be plain HTTP inside the VPC.
Frequently asked questions
What is the difference between a listener and a target group?
A listener is the front door. It defines the port and protocol the ALB accepts connections on (e.g., port 443, HTTPS). A target group is the back door. It defines where traffic is forwarded to: a set of EC2 instances, IP addresses, or Lambda functions. Listener rules sit in between, evaluating each incoming request and deciding which target group receives it.
Can an ALB route to Lambda functions?
Yes. ALB supports Lambda as a target type. When a request matches a rule pointing to a Lambda target group, the ALB invokes the Lambda function with the HTTP request details in the event payload and returns the function response to the client. This works well for serverless HTTP backends that handle occasional bursts of requests.
Does an ALB support WebSockets?
Yes. ALB natively supports WebSocket connections. When a client upgrades an HTTP connection to WebSocket, the ALB maintains the persistent connection and routes subsequent frames to the same backend target. No special configuration is required beyond allowing the WebSocket upgrade in your application.
Why are my ALB health checks failing even though my application is running?
The three most common causes: (1) the target instance security group does not allow inbound traffic from the ALB security group on the health check port, (2) the health check path returns a non-2xx status code, (3) the health check timeout is shorter than your application response time. Check all three before digging further.
Can I use an ALB with Auto Scaling?
Yes, and this is the standard pattern. You create an Auto Scaling group and configure it to register new instances with a specific target group automatically. When the group scales out, new instances register and start receiving traffic once health checks pass. When the group scales in, instances drain in-flight connections before deregistering.