How to Fix Nginx 502 Bad Gateway on AWS ALB / CloudFront Upstream Connection
Quick answer
Nginx returns 502 errors specifically when deployed behind AWS Application Load Balancer or CloudFront, even though the same Nginx configuration works fine...
Nginx returns 502 errors specifically when deployed behind AWS Application Load Balancer or CloudFront, even though the same Nginx configuration works fine when accessed directly. This points at a mismatch somewhere in the chain between CloudFront/ALB and your Nginx instance β commonly timeouts, connection reuse behavior, or health check configuration that behaves differently once AWS's infrastructure sits in front of it.
The Problem
Requests routed through CloudFront or ALB intermittently or consistently return 502, while hitting Nginx's IP directly works fine:
$ curl -I https://your-cloudfront-domain.cloudfront.net/api/data
HTTP/2 502
Nginx's own error log shows the request never actually reached your application, or reached it and got a broken response:
2026/08/07 14:22:03 [error] 8821#8821: *201 upstream prematurely closed connection while
reading response header from upstream, client: 10.0.1.15, server: yourapp.com,
request: "GET /api/data HTTP/1.1", upstream: "http://127.0.0.1:3000/api/data"
Why It Happens
When ALB or CloudFront sits in front of Nginx, several behaviors change compared to a direct client connection, and Nginx's default settings aren't always tuned for this specific topology. Common causes:
- Keepalive timeout mismatches β ALB has its own idle timeout for connections to its targets (default 60 seconds), and if Nginx's
keepalive_timeoutis shorter than ALB's, Nginx can close a connection ALB still considers valid, causing ALB to receive a broken connection and return a 502 to the client. - CloudFront's own origin timeout being shorter than how long your backend genuinely needs to respond, especially for slower endpoints β CloudFront returns a 502 (sometimes 504) when its origin timeout is exceeded, independent of whatever Nginx's own proxy timeouts are configured to allow.
- ALB or CloudFront health checks hitting an endpoint that Nginx doesn't correctly handle, causing target group members to be marked unhealthy and removed from rotation, which manifests as intermittent 502s as traffic gets routed to fewer healthy targets under load.
- HTTP/2 or connection reuse behavior differences between how CloudFront communicates with its origin versus how a typical browser connects directly, occasionally surfacing edge cases in Nginx's proxy configuration that a direct connection never exercised.
The Fix
Align Nginx's keepalive timeout to be longer than ALB's idle timeout, which is the most common specific fix for this exact ALB-plus-Nginx pattern:
http {
keepalive_timeout 65s; # longer than ALB's default 60s target idle timeout
}
Check and, if needed, raise ALB's own idle timeout to match your application's realistic needs, rather than assuming the default is always appropriate:
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:... \
--attributes Key=idle_timeout.timeout_seconds,Value=90
For CloudFront specifically, check and raise the origin response timeout if legitimate requests are being cut off before your backend can respond:
aws cloudfront get-distribution-config --id YOUR_DISTRIBUTION_ID
Update the OriginReadTimeout in the distribution's origin configuration if it's shorter than what your slower endpoints legitimately need (CloudFront allows this to be raised up to 60 seconds via a support request for values beyond the standard range).
Verify your health check endpoint responds quickly and correctly, since a slow or failing health check causes targets to be marked unhealthy, reducing available capacity and increasing 502 rates under load even when the application itself is fine:
location /health {
access_log off;
return 200 "healthy\n";
}
Configure the ALB target group's health check settings to match a reasonably fast, reliable path like this dedicated endpoint rather than a heavier application route:
aws elbv2 modify-target-group \
--target-group-arn arn:aws:elasticloadbalancing:... \
--health-check-path /health \
--health-check-interval-seconds 15 \
--healthy-threshold-count 2
For proxy timeout alignment between Nginx and its own backend, make sure Nginx's timeouts to your application are also generous enough, since a 502 can originate from Nginx giving up on its own upstream just as easily as from ALB or CloudFront giving up on Nginx:
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 60s;
proxy_connect_timeout 60s;
}
Still Not Working?
If 502s persist and seem correlated with traffic spikes rather than being constant, check ALB's target group metrics in CloudWatch for unhealthy host counts and target response time during the affected periods, which tells you whether the issue is capacity-related (too few healthy targets for the load) rather than a pure configuration mismatch:
aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name UnHealthyHostCount \
--dimensions Name=TargetGroup,Value=targetgroup/my-targets/... \
--start-time 2026-08-07T00:00:00Z --end-time 2026-08-07T12:00:00Z \
--period 300 --statistics Maximum
A rising unhealthy host count correlating with the 502 spikes points at either genuine capacity exhaustion under load or a health check that's too aggressive relative to your application's actual response time under real traffic β either scale up your target capacity or relax the health check thresholds to better reflect normal operating conditions rather than continuing to chase the issue purely at the Nginx configuration level.