Content Delivery Network Blog

502 Bad Gateway: What It Means and How to Fix It

Written by Vadym Borovskyi | Nov 2, 2026, 5:52:50 AM

A 502 Bad Gateway means a proxy in your request path, such as nginx, a load balancer or a CDN edge, reached the next server upstream and got back an invalid response or no usable response at all. In practice, most 502s come from three causes: a refused connection, a connection reset, or a malformed or oversized response header. This playbook walks you from symptom to the failing hop and to a verified fix. Plan on 15 to 45 minutes for a typical incident.

Under RFC 9110 (2022), status 502 is reserved for a gateway or proxy that received an invalid response from an inbound server, while upstream timeouts belong to 504. In nginx, a default proxy_read_timeout of 60 seconds that expires produces a 504. A 502 that returns in under 100 ms almost always points to a refused, reset or garbled upstream connection rather than a slow one.

What a 502 bad gateway means at each proxy hop

A 502 is always generated by an intermediary, never by the application that failed. The browser, an edge node, a regional load balancer and your nginx tier can each emit one. That makes the first diagnostic question "which hop wrote this response?" rather than "what is broken?" Answer that, and the search space drops from the whole stack to one connection.

Keep the neighboring codes separate. A 503 means the server or proxy is deliberately refusing work: overload, maintenance, or no healthy backends under some load balancers. A 504 means the upstream accepted the connection and then took too long. A 502 means the conversation with the upstream broke. Some vendors blur these lines. Cloudflare, for example, uses its own 520 to 526 range for origin failures. The mapping still holds as a starting hypothesis.

Common causes of a 502 bad gateway error behind proxies and CDNs

  • Upstream process down or not listening: the app crashed, was OOM-killed, or bound to the wrong interface or port. The proxy gets connection refused instantly.
  • Keepalive race: the upstream closes an idle connection at the same moment the proxy reuses it. The result is an intermittent 502 at low request rates, often below 1% of requests.
  • Worker pool exhaustion: PHP-FPM hitting pm.max_children, or Gunicorn and Puma running out of workers under a traffic spike.
  • Oversized response headers: large cookies or long redirect chains overflow the proxy header buffer, which is 4 KB or 8 KB by default in nginx (one memory page).
  • TLS failure between hops: an expired origin certificate, an SNI mismatch, or a missing intermediate certificate on the origin. CDNs report this as 502.
  • Deploy without draining: old instances leave the pool while still holding in-flight connections.
  • Resolver or routing drift: the proxy cached a DNS answer for an upstream IP that no longer exists, or a security group change blocks the path.

Prerequisites

  • Access to the error log of every proxy you operate: nginx, HAProxy, Envoy, or the load balancer access logs.
  • The origin IP address and the hostname the CDN sends as Host and SNI.
  • A command-line HTTP client that can pin a hostname to a chosen IP and print timing and response headers.
  • Shell access to the upstream host, or at least to its process metrics and kernel log.

Step-by-step 502 error fix checklist

  1. Identify the hop that generated the 502. Inspect the response headers and body. A CDN-branded error page, a vendor request ID header (Amazon CloudFront adds x-amz-cf-id and reports "Error from cloudfront" in X-Cache), or a Server header naming the proxy tells you who wrote it. Why: every later step depends on knowing which connection broke.
  2. Time the failure. Record total response time for several failing requests. Under 100 ms means refused or reset. A time close to a configured timeout means a timeout that some proxy maps to 502. A random spread suggests pool exhaustion. Why: timing narrows the cause before you open a single log.
  3. Bypass the CDN. Use your HTTP client's resolve-override option to send the same hostname, path and headers straight to the origin IP over HTTPS. If the origin answers 200, the fault sits between edge and origin: TLS, SNI, firewall allowlists, or the Host header. If the origin also fails, move inward. Why: this cleanly separates a bad gateway at the CDN from an origin fault.
  4. Read the proxy error log at the matching timestamp. Match the message against the table below. Add $upstream_addr, $upstream_status and $upstream_response_time to your nginx access log format if they are missing. Why: the log line names the exact syscall that failed.
  5. Check the upstream process. Confirm it is listening on the expected address. Search the kernel log for OOM killer entries, and check the FPM or app server log for "reached pm.max_children" or worker timeout warnings. Why: half of all 502 incidents end here.
  6. Fix the keepalive ordering. Make every upstream's idle timeout longer than the idle timeout of the proxy calling it (see the timeout ladder below). Why: this removes the intermittent, hard-to-reproduce class of 502s.
  7. Raise header buffers only if the log asks for it. Increase proxy_buffer_size to 16k and proxy_buffers to match when nginx reports "upstream sent too big header". Then trim the cookies that caused it. Why: bigger buffers cost memory per connection, so treat them as a patch, not the cure.
  8. Drain on deploy. Configure a deregistration delay on the load balancer, longer than your slowest request, and make the app stop accepting new connections on SIGTERM before it exits. Why: this stops the 502 spike that lines up with every release.
nginx error log message Likely cause Fix
connect() failed (111: Connection refused) Upstream not listening, crashed, or wrong port Restart the process, fix the bind address, add a health check
upstream prematurely closed connection while reading response header Keepalive race, or a worker killed mid-request Lengthen the upstream idle timeout, check worker timeouts and OOM kills
recv() failed (104: Connection reset by peer) Upstream or a middlebox reset the socket Compare idle timeouts, inspect conntrack and firewall idle limits
upstream sent too big header while reading response header Response headers exceed proxy_buffer_size Raise the buffer to 16k, shrink cookies
connect() to the PHP-FPM socket failed (11: Resource temporarily unavailable) FPM backlog full, pm.max_children reached Size the pool to memory, raise the listen backlog, fix slow requests
no live upstreams while connecting to upstream Every server marked failed by max_fails Fix the root failure, then review max_fails and fail_timeout
SSL_do_handshake() failed while SSL handshaking to upstream Protocol or cipher mismatch, missing SNI Enable proxy_ssl_server_name, align TLS versions

The error log message identifies the failing syscall, so read it before you touch any timeout or buffer setting.

The timeout ladder: preventing intermittent 502s

The intermittent 502 that never reproduces on demand is usually a keepalive race. Picture a proxy holding an idle pooled connection, and an upstream that closes idle connections sooner than the proxy expects. When both act in the same millisecond, the proxy writes a request into a socket the upstream has already closed. That request fails.

The rule is simple. Each hop's idle timeout must be longer than the idle timeout of the hop that calls it. Defaults break this rule constantly. Node.js uses a server keepAliveTimeout of 5 seconds and Gunicorn a keepalive of 2 seconds. An AWS Application Load Balancer, by contrast, defaults to a 60-second idle timeout.

Hop (outer to inner) Setting Example safe value
CDN edge to origin Origin keepalive timeout (vendor setting) Shortest value in the chain, for example 5 to 30 s
Load balancer Idle timeout 60 s
nginx tier keepalive_timeout 75 s (the nginx default)
Application server Node keepAliveTimeout plus headersTimeout, or Gunicorn keepalive 90 s, with headersTimeout above keepAliveTimeout

Idle timeouts should grow as you move inward, so the caller always closes an idle connection before the server it calls does.

Between nginx and its upstreams, pooled connections only work when the upstream block declares a keepalive count. You also need to set proxy_http_version to 1.1 and clear the Connection header. Without those settings, nginx opens a new connection per request, which hides the race but adds a handshake to every call.

Bad gateway at the CDN: reading which hop failed

When a CDN reports a bad gateway, the edge could not complete a valid exchange with your origin or with its own shield tier. The most common culprits are an origin firewall that allowlists only some edge ranges, an expired or mismatched origin certificate, and an origin that sees the wrong Host header. Amazon CloudFront, for example, returns 502 when it cannot negotiate TLS with the origin.

Load is the other CDN-specific trigger. After a purge or a cold cache, thousands of edges can miss on the same object at once and open connections to an origin sized for a 95% hit ratio. That storm exhausts worker pools and produces exactly the 502s listed above. Origin shield and request coalescing collapse those concurrent misses into one upstream fetch. BlazingCDN includes both on every plan, alongside Prometheus metrics for watching origin error rates, as listed on the BlazingCDN features page for origin shield and request coalescing.

How to validate the fix

  • Plot the 502 ratio per upstream address from $upstream_status. This blog's suggested target is below 0.01% of requests over 24 hours, with no clustering around deploys.
  • Replay traffic with gaps longer than the old upstream idle timeout (for example, one request every 6 seconds against a Node service). Before the fix this reproduces the race; after the fix it should return zero 502s across 1,000 requests.
  • Request the origin directly and through the CDN from at least two regions. Response headers and status should match.
  • Confirm the upstream_response_time distribution did not shift. A falling 502 rate paired with a rising 504 rate means you converted failures rather than fixed them.

Rollback

Change one setting per deploy and keep the previous config version alongside the new one. Before reloading, nginx must pass its configuration test. A reload is graceful, so in-flight requests finish on the old workers. If the 502 ratio rises within 15 minutes, restore the previous file and reload again. For load balancer and CDN settings, record the old values in the change ticket before editing, because vendor consoles rarely keep history.

Trade-offs to accept consciously

Retries hide 502s from users but can multiply load during an incident. Since version 1.9.13, nginx no longer retries non-idempotent methods such as POST on the next upstream unless you add the non_idempotent flag. Leave it off unless your handlers are truly idempotent.

Longer idle timeouts hold more sockets and file descriptors open on the upstream. Larger header buffers raise memory per connection. Generous health check thresholds keep a flapping backend in rotation longer. Each of these settings trades failure visibility for smoothness, so pair every change with an alert on the underlying error.

FAQ: 502 Bad Gateway troubleshooting

Is a 502 Bad Gateway error caused by the client or the server?

A 502 Bad Gateway is a server-side error, generated by a proxy, load balancer or CDN that failed to get a valid response from the server behind it. Clearing the browser cache rarely helps. The one client-side exception is an oversized cookie that inflates response headers past the proxy buffer, which clearing site cookies can temporarily resolve.

What is the difference between a 502 Bad Gateway and a 504 Gateway Timeout?

A 502 Bad Gateway means the upstream connection broke or returned invalid data, while a 504 means the upstream accepted the request but answered too slowly. Timing usually separates them: 502s often return in milliseconds after a refused or reset connection. A 504 arrives close to the configured timeout, such as nginx's 60-second proxy_read_timeout default.

Why does a 502 Bad Gateway appear only intermittently?

Intermittent 502 Bad Gateway errors usually come from a keepalive race, where the upstream closes an idle connection just as the proxy reuses it. They are common when an application keeps a short default, such as Node.js at 5 seconds, behind a load balancer idling at 60 seconds. Making each upstream's idle timeout longer than its caller's removes them.

How do I tell if a CDN or my origin caused the 502 Bad Gateway?

Send the same request directly to the origin IP with the correct Host and SNI. If the origin returns 200, the 502 Bad Gateway comes from the path between CDN and origin: TLS, firewall allowlists or Host handling. If the origin also fails, the fault is yours. Vendor request ID headers and branded error pages also reveal which hop responded.

Run the timeout ladder audit this week

Write down the idle timeout of every hop between your CDN edge and your application process: the edge-to-origin keepalive, the load balancer, nginx, and the app server. Any hop that times out before its caller is a latent 502 source. Fix it, then replay low-rate traffic with gaps longer than the old value and confirm zero 502s across 1,000 requests. Add $upstream_status to your access logs so the next incident starts at step four instead of step one.

If some of your 502s trace back to a single CDN provider rather than your origin, BlazingCDN can run as a second CDN behind DNS with a weighted traffic split, and the BlazingCDN multi-CDN integration guide describes how that setup works.