breakitdown

502 Bad Gateway

Answer

502 Bad Gateway comes from a proxy or load balancer: it is up, but the server behind it (the upstream) refused the connection, closed it, or sent back something that was not valid HTTP. Look at the upstream, not the proxy.

502
Bad Gateway RFC 9110 §15.6.3

A proxy or gateway got an invalid response, or no response, from the server behind it.

The proxy (nginx, a load balancer, a CDN) is working; the upstream application is not answering properly. Usual causes: the app process is down or restarting, it listens on a different port or socket than the proxy expects, or it crashed mid-request. Check the proxy's error log for the reason, then the upstream's logs.

Explained

There are two hops: client to proxy, and proxy to upstream. A 502 means the second hop failed fast. Compare 504, where the upstream was reachable but too slow, and 503, where the proxy knows there is no usable upstream.

nginx writes the exact reason to its error log. connect() failed (111: Connection refused) while connecting to upstream means nothing is listening at the address in proxy_pass or fastcgi_pass. upstream prematurely closed connection while reading response header means the app accepted the request and then died or closed the socket. no live upstreams means every server in the upstream block is marked as failed.

A common pattern with Gunicorn or similar worker servers: a request runs past the worker timeout (30 seconds by default in Gunicorn), the master kills the worker, and nginx reports a 502 for that request. Raising nginx timeouts does not help here; the limit is in the app server.

On AWS, an Application Load Balancer returns 502 when the target closes the connection or sends a malformed response, and also when the target's keep-alive timeout is shorter than the load balancer's idle timeout, so the target closes connections the balancer still considers open.

How to debug it

  1. Read the proxy's error log for the matching request; the message names the failure (refused, reset, closed, invalid header).
  2. From the proxy host, call the upstream directly, for example curl -v http://127.0.0.1:8000/, using the exact address and port in the proxy config.
  3. Check that the app is running and has not been restarted by a crash loop or an out-of-memory kill (dmesg, journalctl, container restart counts).
  4. Look for app-server worker timeouts or crashes at the same timestamp in the upstream's logs.
  5. With containers, confirm the app listens on 0.0.0.0 rather than 127.0.0.1 inside the container, and that the proxy uses the right host and port.
  6. If the upstream returns very large headers (big cookies), raise proxy_buffer_size; nginx logs upstream sent too big header in that case.

Gotchas

Related explainers

← All HTTP status codes