502 Bad Gateway from an Application Load Balancer
Fix ALB 502 Bad Gateway: turn on access logs, then raise the target's keep-alive above the ALB idle timeout. Also covers health checks and security groups.
502 Bad Gateway
Incident Overview
Fix ALB 502 Bad Gateway: turn on access logs, then raise the target's keep-alive above the ALB idle timeout. Also covers health checks and security groups.
hard fix·5 min read
By Vishvesh Patel·DevOps Engineer·Updated 21 September 2026
502 Bad Gateway elb_status_code=502 target_status_code=- error_reason=- target_status_code=502 error_reason=AWSALBTargetGroupUnhealthy upstream prematurely closed connection while reading response header from upstream
Do this first3 steps
Run these in order. Each one tells you what its output means before you change anything.
• Read the ALB access log, which distinguishes the causes
Field 9 is elb_status_code and field 10 is target_status_code. A 502 with target_status_code of - means the target never returned a usable response, which is different from the target itself returning 502.
• Compare the ALB idle timeout against your application's keep-alive
The application's keep-alive must be longer than the ALB's idle timeout. If the target closes a pooled connection first, the ALB sends a request onto a socket that is closing and returns 502. This causes most intermittent 502s.
• Bypass the ALB to see whether the target is healthy on its own
Run this from an instance in the VPC. A clean response direct from the target points at the ALB-to-target relationship rather than the application, which narrows it to timeouts, protocol or security groups.
A 502 from an ALB means the load balancer reached your target and could not parse or complete the response. That is different from a 503, which means no healthy target existed, and a 504, which means the target did not answer in time.
aws s3 cp s3://acme-alb-logs/AWSLogs/123456789012/elasticloadbalancing/eu-west-1/2026/09/21/ - --recursive | zcat | awk '$9==502' | head
Turn on access logs first
Without them you are guessing.
Field 9 is elb_status_code, field 10 is target_status_code. The distinction is the whole diagnosis:
The second means look at your app. The first means look at the connection.
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn <arn> \
--attributes Key=access_logs.s3.enabled,Value=true \
Key=access_logs.s3.bucket,Value=acme-alb-logsThe keep-alive mismatch, which causes most intermittent 502s
The ALB pools connections to targets. If the target closes a pooled connection just as the ALB sends a request onto it, the request dies and the ALB returns 502.
The rule: the target's keep-alive timeout must be longer than the ALB's idle timeout.
Default is 60 seconds. Common application defaults are shorter, which is exactly backwards:
Node.js is the classic:
headersTimeout must exceed keepAliveTimeout or Node closes the socket anyway.
This produces 502s at a low, steady rate that correlate with nothing, which is what makes them hard to chase. If your 502 rate is a fraction of a percent and the application logs show nothing, check this before anything else.
aws elbv2 describe-load-balancer-attributes --load-balancer-arn <arn> \ --query "Attributes[?Key=='idle_timeout.timeout_seconds']"
Health checks and deregistration
A target being removed mid-request returns 502 or a reset. Deregistration delay lets in-flight requests finish:
The delay must exceed your longest request, and your application must handle SIGTERM by draining rather than exiting immediately. A container that exits on the first SIGTERM drops every in-flight request no matter what the delay is set to.
aws elbv2 modify-target-group-attributes \ --target-group-arn <arn> \ --attributes Key=deregistration_delay.timeout_seconds,Value=60
Response problems
The ALB rejects malformed responses:
Run that from inside the VPC. A clean response there with 502s through the ALB points at headers or timing.
curl -sv -o /dev/null -m 5 http://10.0.1.42:8080/ 2>&1 | tail -20
HTTPS to the target
If the target group protocol is HTTPS, the target must actually serve TLS on that port. A plain HTTP server behind an HTTPS target group returns 502 immediately and consistently.
The ALB does not validate the target's certificate, so self-signed is fine. It does require TLS to be there.
aws elbv2 describe-target-groups --target-group-arns <arn> \
--query 'TargetGroups[].{Protocol:Protocol,Port:Port,HealthPath:HealthCheckPath}'Security groups
Target.Timeout in the health description means the ALB cannot reach the target at all, which is usually a security group. The target's group must allow inbound on the target port from the ALB's security group, not from a CIDR.
aws elbv2 describe-target-health --target-group-arn <arn>
A checklist
Frequently Asked Questions
What is the difference between 502, 503 and 504 from an ALB?
502 means the ALB connected to a target and got back something it could not use, including a connection closed mid-response. 503 means there were no healthy targets to send the request to at all. 504 means a target accepted the request and did not respond within the idle timeout. They point at completely different things: 502 at the response or the connection, 503 at target registration and health checks, and 504 at application latency.
Why do I get occasional 502s with nothing in my application logs?
Almost certainly a keep-alive mismatch. The ALB pools connections to targets, and if your application's keep-alive timeout is shorter than the ALB's idle timeout, the target closes a pooled connection at the moment the ALB is placing a request on it. The request fails before your application sees it, which is why the logs are empty. Set the application's keep-alive above the ALB's idle timeout, which defaults to 60 seconds; Node.js is the usual offender with a 5 second default.
Why does Node.js need both keepAliveTimeout and headersTimeout set?
keepAliveTimeout controls how long an idle connection is kept open, and headersTimeout caps how long the server waits for the complete request headers. If headersTimeout is shorter than or equal to keepAliveTimeout, Node can close a connection while the ALB is still sending, which reintroduces the same race you were trying to eliminate. The convention is to set headersTimeout slightly above keepAliveTimeout, for example 65000 and 66000 milliseconds against a 60 second ALB idle timeout.
Why do I see 502s only during deployments?
Targets are being deregistered while requests are still in flight. Set deregistration_delay.timeout_seconds on the target group to longer than your slowest request so the ALB stops sending new traffic and waits for existing requests to finish. That is only half of it: the application must also handle SIGTERM by refusing new work and finishing what it has, rather than exiting immediately. A container that dies on the first signal drops in-flight requests regardless of the delay.
Does the ALB validate my target's TLS certificate?
No. If the target group protocol is HTTPS, the ALB requires the target to speak TLS but does not verify the certificate, so self-signed certificates are fine for the load balancer to target hop. What does fail, immediately and consistently, is a target group configured for HTTPS in front of a target serving plain HTTP. Check that the target group's protocol and port match what the application actually listens with before investigating anything more subtle.
Reference and practice
Learn the underlying concept
Other AWS errors
Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.
Get notified about new tutorials and features.
• Roadmaps
• Interview Prep
• Certifications
• Company
© 2026 DevOpsLesson. All rights reserved.
Need to practice this scenario?
Simulate this error condition and practice recovery commands safely in our in-browser terminal.