Traffic goes only to healthy targets
A target Security Group should allow the application port from the LB Security Group, not the entire Internet. HTTP 503 commonly means no healthy or available targets; investigate 4xx with client requests and 5xx with LB metrics, access logs, and application logs.
flowchart LR C["Client<br/>HTTPS"] --> L["ALB<br/>ACM certificate"] L --> SG["Target SG<br/>8080 from sg-alb"] SG --> T["Healthy target<br/>/health = 200"] T -- "deregister" --> D["Draining<br/>finish in-flight requests"] L -. "no healthy targets" .-> E["HTTP 503"] %% step C: The browser connects with TLS to the load balancer. %% step L: The ALB terminates TLS with a certificate from ACM and opens a new connection to the target. %% step SG: The target's Security Group allows the app port only from the ALB's Security Group. %% step T: Only targets passing the health check receive requests. %% step D: A deregistering target gets no new requests but finishes the open ones during the deregistration delay. %% step E: If every target is unhealthy or missing, the ALB answers 503 itself.
Health checks
Each target group defines a protocol, port, path (for example /health), success codes, interval, and healthy/unhealthy thresholds. A good health endpoint is cheap and checks what the app really needs, but should not fail just because a non-critical dependency is slow, or every target may be marked unhealthy at once. Auto Scaling can use the ELB health check (--health-check-type ELB) to replace targets that fail it.
TLS
A TLS listener uses an ACM certificate (free for public certificates, renewed automatically); SNI supports multiple domains and certificates on one ALB or NLB. A security policy chooses the TLS versions and ciphers; prefer a recent policy that disables old protocols. You can terminate TLS at the ALB (simplest) or re-encrypt to the targets when traffic must stay encrypted end to end.
Stickiness and draining
Stickiness keeps a client on one target with a cookie (load balancer-generated or application-based) but can skew load and breaks if that target dies. Deregistration delay (connection draining, default 300 seconds) lets in-flight requests finish before removal; shorten it for fast, short requests to speed up deployments.
Slow start gives a newly healthy target a gradually increasing share of traffic so it can warm caches.
Cross-zone load balancing
With cross-zone enabled, each LB node spreads traffic across targets in all AZs, so uneven instance counts per AZ do not overload anyone. Defaults and data charges differ by LB type (on for ALB, off by default for NLB), so inspect configuration instead of relying on old defaults.
Observability
| Signal | What it tells you |
|---|---|
HTTPCode_ELB_5XX_Count |
Errors generated by the LB itself (no targets, timeouts) |
HTTPCode_Target_5XX_Count |
Errors returned by your application |
TargetResponseTime |
Latency of the targets |
UnHealthyHostCount |
Targets failing health checks |
| Access logs in S3 | Every request: client, path, status, timings |
AWS WAF can be attached to an ALB to block common web attacks and rate-limit clients.
| Command | Purpose |
|---|---|
aws elbv2 modify-target-group-attributes --target-group-arn ... --attributes Key=deregistration_delay.timeout_seconds,Value=30 |
Change delay or stickiness |
aws elbv2 describe-target-group-attributes --target-group-arn ... |
View attributes |
aws elbv2 modify-target-group --target-group-arn ... --health-check-path /health |
Change the health check |
Traps: opening the target port to 0.0.0.0/0; reading every 5xx as an app bug; a health check that fails when one optional dependency is slow.