Runbooks / alb-5xx
ALB 5xx spike
5xx on the Application Load Balancer: split ELB-generated 502/503/504 from target-side errors first — they have completely different fixes.
12 canonical sections · 4 command blocks · last reviewed · updated 2026-09-21
Related focus areas:CloudOpsNetOpsSRE
Fix the underlying gap:AWS Security ImprovementCloud Cost OptimizationIncident Management MaturityObservability MaturityPlatform Engineering AdoptionProduction-Ready KubernetesResilience and DR ImprovementSRE Adoption
On this page
Symptoms
- Alarms on
HTTPCode_ELB_5XX_CountorHTTPCode_Target_5XX_Countfire; users see 502/503/504 on ALB-fronted paths. - Often correlates with a deploy, a target group change, or a slow backend; sometimes appears as "the site is down" with the app itself feeling fine to internal checks.
- Code semantics matter before anything else: 502 = target closed/invalid response, 503 = no healthy targets, 504 = target timeout (idle timeout exceeded).
Severity / impact
- Sev-1 when user-facing traffic fails outright; 503 with zero healthy targets is a full outage of that path.
- Sev-2 for partial 5xx (a percentage, one target pool, one region) or 504s on a non-critical async path.
Immediate safety actions
- Identify ELB-generated vs target-generated 5xx from CloudWatch BEFORE touching anything:
HTTPCode_ELB_502/503/504_CountvsHTTPCode_Target_5XX_Count. The fix sets are disjoint. - If a deploy or deregistration just happened, roll back / pause the rollout — target churn is the most common self-inflicted 5xx.
- Do not "restart the ALB" — there is no such action; changing listener/routing config mid-triage adds a variable.
Fast checks
- Target health for the ALB's target groups: how many healthy, and why are the rest failing (Target.Timeout? Target.FailedHealthChecks? Elb.InternalError)?
- 503 pattern: healthy count zero for a group = nothing to route to (SG/NACL/registration/deregistration).
- 504 pattern:
TargetResponseTimep99 climbing toward the idle timeout = backend slowness, not the ALB. - 502 pattern: app crashing responses (early close, invalid headers) — often an app-level error correlated with a release.
Diagnostic commands
# Target health per group — state + reason code says which fix branch you are on:
aws elbv2 describe-target-health --target-group-arn "$TG_ARN" \
--query 'TargetHealthDescriptions[].{Target:Target.Id,Port:Target.Port,State:State,Reason:ReasonCode,Description:Description}'# ELB-generated 504s (idle-timeout) vs target 5xx over the last hour, 1-min sums:
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name HTTPCode_ELB_504_Count \
--dimensions Name=LoadBalancer,Value="$ALB_ARN" --statistics Sum --period 60 \
--start-time "$(date -u -d '1 hour ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--query 'Datapoints[].{t:Timestamp,v:Sum}' --output table# The idle timeout and routing attributes actually configured:
aws elbv2 describe-load-balancer-attributes --load-balancer-arn "$ALB_ARN" \
--query 'Attributes[?contains(Key, `timeout`) || contains(Key, `deregistration`)]'# Which instances are UNHEALTHY and for how long (from your access-log sink):
aws s3 ls "s3://$ALB_LOGS_BUCKET/AWSLogs/$ACCOUNT_ID/elasticloadbalancing/$(date -u +%Y/%m/%d)/" | tail -5Likely causes
1. No healthy targets → 503: registration broke, health checks fail (wrong path/port), or SG/NACL blocks the checker (most common after infra changes). 2. Backend too slow → 504: target p99 exceeds the idle timeout (default 60s) — slow endpoint, lock contention, downstream saturation. 3. Target connection broken → 502: app crashing, keep-alive shorter than the ALB's idle timeout (ALB reuses a connection the app already closed), or invalid responses (headers/protocol). 4. Deregistration storm: deploy replaced all targets at once; in-flight requests hit closing connections → burst of 502/503. 5. True backend 5xx: target app erroring — visible in HTTPCode_Target_5XX_Count and the app's own logs; the ALB is the messenger.
Decision tree
- 503 with 0 healthy targets → (a): fix registration/health-check/SG; the moment one target passes, traffic resumes.
- 504 with rising
TargetResponseTime→ (b): chase the backend slowness (its own runbook); raise idle timeout ONLY if slow-but-valid responses are intentional. - 502 right after deploys → (c): add connection draining, fix the app's keep-alive, slow the rollout.
- 502 persistent on one path → app crash/protocol issue: read the target's own error logs (d).
HTTPCode_Target_5XX_Countmirrors app errors 1:1 → stop blaming the LB; open the application's incident.
Remediation
- (a) Health: correct the health-check path/port/interval to match what the app actually serves (a 200 on /healthz, not a redirect); open the SG to the ALB's subnets or the VPC CIDR for the check port; re-register targets and confirm
healthy≥ replicas per AZ. - (b) Timeouts: raise the ALB idle timeout above the backend's slowest legitimate response (listener attribute
routing.http.idle_timeout) and cap the BACKEND's own timeout below it — the app must fail before the LB gives up, or users see 504 instead of a clean app error. Fix the underlying slowness as its own item. - (c) Draining: ensure deregistration delay (default 300s) is not fighting a fast-scaled deployment; deploy with rolling updates so N-1 targets stay healthy; app keep-alive ≥ ALB idle timeout.
- (d) App-side 502s: fix the crash/invalid response; check for response headers that violate HTTP (control chars, oversized) which the ALB refuses to forward.
- Communicate which class it was — "ALB 5xx" in the postmortem without the ELB/target split sends the next responder down the wrong path.
Verification
- 5xx of the identified class at zero (or pre-incident baseline) for 30+ minutes under real traffic.
- Target group shows expected healthy counts across AZs;
HealthyHostCountstable through a test deploy. - If timeouts were changed: p99 still within SLO and no new 504 class appearing at the new boundary.
Escalation
- Service owners for target-side errors and slowness; network/platform for SG/NACL/registration; AWS support (via your account's support path) only when metrics show ELB-internal errors (
Elb.InternalError) — that is the one genuinely AWS-owned branch. - Hand-off state: ELB vs target 5xx counts with timestamps, target health snapshot, idle timeout + deregistration delay values, recent deploys.
Prevention
- Alarm on
HTTPCode_ELB_5XX_CountANDHTTPCode_Target_5XX_Countseparately; alert onHealthyHostCount < desiredper group. - ALB access logs on (they name elb_status_code vs target_status_code per request — the split, precomputed).
- Connection draining + rolling deploys as defaults; keep-alive and timeout budget documented per service (app < ALB idle < client).
Sources
Know which failure modes you actually have?
A free OpsRoadmaps assessment scores your controls with explainable findings and builds a prioritized roadmap — runbook coverage is one of the things it measures.
Assess my stackRunbooks document self-reported practice. Commands and thresholds were last reviewed 2026-09-19 — verify against your own environment before acting.