Load Balancing and Route 53¶
What You'll Learn¶
- When to use an Application Load Balancer or a Network Load Balancer
- How listeners, rules, target groups, and health checks fit together
- How to issue and attach free TLS certificates with ACM
- How Route 53 alias records and routing policies send users to healthy endpoints
ALB or NLB?¶
| Application Load Balancer | Network Load Balancer | |
|---|---|---|
| Layer | 7 — HTTP, HTTPS, gRPC, WebSockets | 4 — TCP, UDP, TLS |
| Routing | Host, path, headers, query strings, methods, source IP | Port only |
| TLS | Terminates | Terminates (TLS listener) or passes through (TCP listener) |
| Static IPs | No — use DNS names | Yes — one per AZ, or your own Elastic IPs |
| Client IP at target | In X-Forwarded-For |
Preserved (for instance and IP targets), or via PROXY protocol |
| Extra features | Redirects, fixed responses, authentication with OIDC or Cognito, WAF | Very high throughput, low latency, PrivateLink endpoint services |
| Typical use | Web apps and APIs | Non-HTTP protocols, static IPs for allow-lists, extreme scale |
The concepts behind these choices are in Load Balancers and Reverse Proxies.
How an ALB Is Put Together¶
flowchart LR
C["Client"] --> L443["Listener :443<br/>ACM certificate"]
L80["Listener :80"] -->|"redirect to HTTPS"| L443
L443 --> R1{"Rule: Host api.example.com<br/>and path /orders/*"}
L443 --> R2{"Rule: Host api.example.com<br/>and path /payments/*"}
L443 --> RD["Default action:<br/>fixed 404"]
R1 --> TG1["Target group: orders<br/>health check /healthz"]
R2 --> TG2["Target group: payments"]
TG1 --> T1["ECS tasks, instances, IPs, or Lambda"]
resource "aws_lb" "public" {
name = "public-web"
load_balancer_type = "application"
subnets = aws_subnet.public[*].id
security_groups = [aws_security_group.alb.id]
drop_invalid_header_fields = true
access_logs {
bucket = aws_s3_bucket.lb_logs.id
enabled = true
}
}
resource "aws_lb_target_group" "orders" {
name = "orders"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
target_type = "ip" # ECS on Fargate and EKS pods use IP targets
health_check {
path = "/healthz"
matcher = "200"
interval = 15
healthy_threshold = 2
unhealthy_threshold = 3
timeout = 5
}
deregistration_delay = 30 # drain in-flight requests before removing a target
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.public.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate_validation.api.certificate_arn
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
status_code = "404"
}
}
}
resource "aws_lb_listener_rule" "orders" {
listener_arn = aws_lb_listener.https.arn
priority = 10
action {
type = "forward"
target_group_arn = aws_lb_target_group.orders.arn
}
condition {
host_header { values = ["api.example.com"] }
}
condition {
path_pattern { values = ["/orders/*"] }
}
}
resource "aws_lb_listener" "http_redirect" {
load_balancer_arn = aws_lb.public.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
Health Checks and Idle Timeouts¶
- A target is taken out of rotation after
unhealthy_thresholdconsecutive failures, and returned afterhealthy_thresholdsuccesses. - If every target in a group is unhealthy, an ALB fails open and sends traffic to all of them anyway, on the basis that some responses beat none. Don't rely on it — alert on
UnHealthyHostCount. - The ALB's idle timeout defaults to 60 seconds. Application keep-alive timeouts must be longer, or you'll get intermittent
502errors. See Timeouts.
Reading ALB errors¶
| Metric or code | Source | Meaning |
|---|---|---|
HTTPCode_ELB_5XX_Count with 502 |
The load balancer | Target closed the connection or returned an invalid response |
HTTPCode_ELB_5XX_Count with 503 |
The load balancer | No registered or healthy targets for the rule |
HTTPCode_ELB_5XX_Count with 504 |
The load balancer | Target didn't respond before the idle timeout |
HTTPCode_Target_5XX_Count |
Your application | The app itself returned a 5xx |
TargetResponseTime |
Latency measured from the load balancer |
The split between ELB and target 5xx codes tells you immediately whether to look at infrastructure or application logs. ALB access logs record the target, status codes from both sides, and timings for each request.
TLS Certificates With ACM¶
AWS Certificate Manager issues public certificates at no charge for use with load balancers, CloudFront, and API Gateway, and renews them automatically.
resource "aws_acm_certificate" "api" {
domain_name = "api.example.com"
subject_alternative_names = ["*.api.example.com"]
validation_method = "DNS"
lifecycle { create_before_destroy = true }
}
resource "aws_route53_record" "api_validation" {
for_each = {
for o in aws_acm_certificate.api.domain_validation_options : o.domain_name => o
}
zone_id = data.aws_route53_zone.main.zone_id
name = each.value.resource_record_name
type = each.value.resource_record_type
records = [each.value.resource_record_value]
ttl = 300
}
resource "aws_acm_certificate_validation" "api" {
certificate_arn = aws_acm_certificate.api.arn
validation_record_fqdns = [for r in aws_route53_record.api_validation : r.fqdn]
}
Keep the DNS validation records in place permanently — ACM uses them for every automatic renewal. CloudFront requires certificates in us-east-1.
Route 53¶
Alias records¶
Point a domain — including the zone apex — at an AWS resource:
resource "aws_route53_record" "api" {
zone_id = data.aws_route53_zone.main.zone_id
name = "api.example.com"
type = "A"
alias {
name = aws_lb.public.dns_name
zone_id = aws_lb.public.zone_id
evaluate_target_health = true
}
}
Alias records follow the load balancer's changing IP addresses automatically, work at the apex where CNAMEs can't, and don't incur query charges for AWS targets.
Routing policies¶
| Policy | Behavior | Use for |
|---|---|---|
| Simple | One answer | Most records |
| Weighted | Split traffic by weight | Blue-green and canary migrations between stacks |
| Latency | The region with the lowest latency for the user | Multi-region active-active |
| Failover | Primary while healthy, otherwise secondary | Active-passive disaster recovery |
| Geolocation | By user's country or continent | Data residency, localized content |
| Geoproximity | By distance, with adjustable bias | Shifting load between regions gradually |
| Multivalue answer | Up to eight healthy records | Simple client-side load distribution |
| IP-based | By the client's source network | Routing specific ISPs or networks |
Failover between regions¶
resource "aws_route53_health_check" "primary" {
fqdn = "api-eu.example.com"
type = "HTTPS"
resource_path = "/healthz"
request_interval = 10
failure_threshold = 3
}
resource "aws_route53_record" "api_primary" {
zone_id = data.aws_route53_zone.main.zone_id
name = "api.example.com"
type = "A"
set_identifier = "primary-eu-west-1"
health_check_id = aws_route53_health_check.primary.id
failover_routing_policy { type = "PRIMARY" }
alias {
name = aws_lb.eu.dns_name
zone_id = aws_lb.eu.zone_id
evaluate_target_health = true
}
}
resource "aws_route53_record" "api_secondary" {
zone_id = data.aws_route53_zone.main.zone_id
name = "api.example.com"
type = "A"
set_identifier = "secondary-us-east-1"
failover_routing_policy { type = "SECONDARY" }
alias {
name = aws_lb.us.dns_name
zone_id = aws_lb.us.zone_id
evaluate_target_health = true
}
}
DNS failover depends on clients honoring TTLs, so it takes a minute or more. The secondary region must actually be able to serve — test failover regularly.
Common Mistakes¶
- Choosing an NLB for an HTTP service and losing path routing, redirects, and per-request metrics — or an ALB when partners need static IPs.
- Health check paths that hit the database, draining every target during a dependency blip.
- Application keep-alive timeouts shorter than the ALB idle timeout.
- Deleting ACM DNS validation records after issuance, so automatic renewal fails months later.
- CNAME records at the apex or to load balancer IPs instead of alias records.
- DNS failover configured but never tested, pointing at a secondary region that can't take the load.
- No access logs, so a spike of
502s can't be traced to specific targets.
Interview Questions¶
- When would you choose an NLB over an ALB?
- An ALB is returning
503. What are the likely causes, and how do the CloudWatch metrics help? - How does ACM renew certificates automatically, and what can break it?
- What's the advantage of a Route 53 alias record over a CNAME?
- Design active-passive failover between two regions using Route 53.
Next¶
Continue to S3 and Storage.