ALB returns 504 Gateway Timeout — backend too slow
Requests return 504 from the load balancer at steady rate
Focused incident mode — strips away everything but this runbook.
Symptoms
- Requests return 504 from the load balancer at steady rate
- Specific slow endpoints time out while others work
- Backend is up but takes longer than the LB idle timeout
The 504 is the LB telling you it waited its full budget. Raising idle_timeout treats the symptom; a backlog of genuinely slow endpoints will surface latency elsewhere — fix the slow path too.
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
LB attributes (idle timeout)
aws elbv2 describe-load-balancer-attributes --load-balancer-arn <LB-ARN>Backend response time
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name TargetResponseTime --dimensions Name=LoadBalancer,Value=<LB-ARN> Name=TargetGroup,Value=<TG-ARN> --start-time <ISO> --end-time <ISO> --period 300 --statistics Maximum
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- Only long requests (reports, uploads, streams) 504
- Idle timeout configured at or below app p99
Confirm before fixing
aws elbv2 describe-load-balancer-attributes --load-balancer-arn <LB-ARN>Current idle timeout
Fix
aws elbv2 modify-load-balancer-attributes --load-balancer-arn <LB-ARN> --attributes Key=idle_timeout.timeout_seconds,Value=<LARGER-SECONDS>Raise the idle timeout, or split the workload so streams use a dedicated LB
Expected: Attributes updated
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
aws elbv2 describe-load-balancer-attributes --load-balancer-arn <LB-ARN>Read the timeout baseline
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name TargetResponseTime --dimensions Name=TargetGroup,Value=<TG-ARN> --start-time $(date -u -d '-1 hour' +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 300 --statistics MaximumQuantify backend latency
aws elbv2 modify-load-balancer-attributes --load-balancer-arn <LB-ARN> --attributes Key=idle_timeout.timeout_seconds,Value=<LARGER-SECONDS>Raise the timeout to match real workloads
aws ssm send-command --instance-ids <TARGET-INSTANCE-ID> --document-name AWS-RunShellScript --parameters 'commands=["time curl -s -o /dev/null http://localhost:<PORT>/<ENDPOINT>"]'Keep the app-latency investigation going from the host
Prevention
- Set idle_timeout above your worst real endpoint, and alarm on TargetResponseTime.
- Move long-running work to async jobs with a 202 + poll pattern.
- Profile slow endpoints (db queries, cold caches) before they cross the timeout line.
Related runbooks
Same stack, same failure family — worth a skim.
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.