502
How to fix HTTP 502 Bad Gateway
A proxy in front of the application answered on its behalf, because the application's own answer was missing, truncated or not valid HTTP.
Why you are seeing 502, and what to do about it
The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.
Front-end developer
Why you are seeing it
Your request did not fail while requesting, it failed while parsing. The proxy's 502 page is HTML, so `res.json()` throws `Unexpected token '<'` and the stack trace points at your parser instead of at the server that broke. It tends to hit one endpoint rather than the whole site — the slow one, the one behind a cold-starting container, the one somebody just deployed — and it can appear and vanish between two page loads while the origin restarts underneath you.
What to do
Stop trusting the body before the status. Check `res.ok` and read `content-type` before parsing, because a 502 body is `text/html` and your API's real errors are not. Then open the failed request in the network panel and read the response headers: `server`, `via` and `cf-ray` name whoever wrote the page, which is the entire question. Retry only idempotent requests, with backoff and a cap, since a retry storm against a restarting origin is what turns a ten-second blip into an outage. And do not spend the afternoon on CORS — an error page carries no `access-control-allow-origin`, so the browser reports a CORS failure stacked on top of the real one, and the CORS message is the symptom rather than the cause.
Back-end developer
Why you are seeing it
Your proxy could not get a usable answer out of your application, and in nginx the error log line is the diagnosis. There are only a few of them worth memorising. `connect() failed (111: Connection refused) while connecting to upstream` means nothing is listening at the address in `proxy_pass`. `upstream prematurely closed connection while reading response header from upstream` means the worker died mid-request — an out-of-memory kill, an unhandled exception in a synchronous worker, or a request that outlived the application server's own timeout. `upstream sent too big header while reading response header from upstream` means the response headers exceeded `proxy_buffer_size`, which is what an oversized `Set-Cookie` does.
What to do
Read the proxy's error log before anything else — it names both the reason and the upstream address, and that pair answers the question faster than any amount of application logging. Then check that the two ends agree: which address and port is the application actually bound to, and is it the one in `proxy_pass`? An application listening on `127.0.0.1:3000` is unreachable from a proxy in another container or network namespace, and that is the same refusal as a crashed process. If the log says the connection closed early, look at the application's own request timeout and its memory limit; if it says the header was too big, raise `proxy_buffer_size` and find out why the header grew. On Cloudflare, check whether the error page is Cloudflare-branded: Cloudflare documents that a branded page means Cloudflare produced the error, while an unbranded one came from your origin and was only relayed.
End user
Why you are seeing it
Nothing on your side is broken, and nothing you do locally will change it. A 502 means the website's own machines could not talk to each other, so the message you are reading was written by the layer sitting in front of the site while the site itself was unreachable. It usually lasts seconds to minutes, typically during a deploy or a crash, and it often affects only part of a site — checkout fails while the home page loads perfectly.
What to do
Wait a minute and reload. Clearing cookies, switching browser, flushing DNS and restarting the router are all wasted effort here, because the failure is at the other end of the connection. If it lasts more than a few minutes it is worth reporting, and the useful details to include are the exact time and any `cf-ray` value or request id printed on the error page, since that is the string the site's operators can actually look up. For a paid service, the status page is the fastest way to find out whether they already know.
Reproduce 502 with curl
Run this against the URL that failed. It prints the status without the error page, so you can see what the server said rather than what the browser rendered.
curl -sS -o /dev/null -D - -w '\n%{http_code} in %{time_total}s\n' https://example.com/api/`-sS` silences the progress meter while keeping error messages, `-o /dev/null` discards the HTML error page, `-D -` dumps the response headers to stdout, and `-w` prints the status and the wall-clock duration once the transfer finishes. Three things in that output decide where to look next. The status line confirms it really is 502 and not a 500 your application produced itself. The header block names the author — `server: nginx` with no `via` is your own proxy, while `server: cloudflare` with a `cf-ray` is Cloudflare, and that `cf-ray` value is the id their support will ask for. And the elapsed time is the tiebreaker against 504: a refused or broken upstream answers in milliseconds, whereas a timed-out one answers on a timer, which is 60 seconds for nginx's default `proxy_read_timeout` and 125 seconds before Cloudflare emits its own 524. To prove the proxy rather than the origin is at fault, run the same command with `--resolve example.com:443:203.0.113.10`, which connects to the origin address you name while still sending the original host name and SNI.
What 502 actually means
RFC 9110 §15.6.3 defines 502 as the status a server returns while acting as a gateway or proxy, when it received an invalid response from an inbound server it accessed while trying to fulfil the request. Two things follow from that wording and both change where you look. First, the code is written by the intermediary — nginx, HAProxy, Envoy, an application load balancer, Cloudflare — and not by your application, which may never have run at all. Second, an invalid response is a much wider category than an error response: a refused TCP connection, a connection closed before the response headers were complete, a status line the proxy cannot parse, and headers larger than the proxy's buffer all qualify, while an application that successfully returns its own 500 does not, because that is a valid response and the proxy relays it untouched. 502 is also not a timeout — RFC 9110 §15.6.5 reserves 504 for a response that did not arrive in time — and that distinction has a stopwatch attached: a 502 usually comes back in milliseconds, while a 504 comes back at exactly whatever timer expired. Finally, 502 is not in the list of heuristically cacheable status codes in RFC 9110 §15.1, so an intermediary will not cache it on its own initiative, and nginx will retry the next upstream on `error` or `timeout` by default but will not retry a non-idempotent request unless `proxy_next_upstream` is given the `non_idempotent` parameter.
Codes 502 gets mistaken for
| Confused with | How to tell them apart |
|---|---|
| 504 | Both are a proxy reporting on an upstream, and RFC 9110 draws the line for you: 502 is an invalid response, 504 is no timely response. Read the clock rather than the wording. A 502 usually returns in well under a second, because the upstream refused the connection or dropped it; a 504 returns on a round number — 60 seconds for nginx's default `proxy_read_timeout`, 125 seconds for Cloudflare — because something sat and waited out a timer. |
| 500 | 500 is the application admitting its own failure; 502 is the proxy reporting that the application never handed it a usable answer. If your framework's error handler ran, the status is 500, its logs hold the exception, and the proxy passes that response through untouched. A 502 often means the worker died before any handler could run, so the application log is empty and the proxy's log is the only record that anything happened. |
| 503 | 503 says the server is temporarily unable to handle the request, and it is usually deliberate — maintenance mode, a health check that pulled a backend out of rotation, or a rate limiter. RFC 9110 lets it carry `Retry-After`, and 502 has no such convention. So 503 means something decided to refuse you, while 502 means something failed to answer at all. |
| 521 | 521 is Cloudflare's own code rather than an IETF one, and it exists precisely so this case is not reported as a generic 502: it means Cloudflare's connection to your origin was refused outright. Getting 521 instead of 502 narrows the problem to the origin's listener or its firewall in a single step, which is the reason the 520-526 range was invented at all. |
nginx | nginx writes the reason into its error log before it writes the 502 to the client, and that line is far more specific than three digits. `connect() failed (111: Connection refused)`, `upstream prematurely closed connection`, `upstream sent too big header` and `no live upstreams` are four different problems that all reach the browser looking identical. |
- Reason phrase
- Bad Gateway
- Class
- 5xx server error
- Defined by
- RFC 9110
- Standard
- IETF standard
Tools on this site
All of these run in your browser — nothing is uploaded.
- Port Number Reference — check what should be listening on the port your `proxy_pass` dials, and find what is not
- Certificate Decoder — read the origin certificate when the proxy rejects the upstream's TLS rather than its HTTP
- PCAP Viewer — open a capture between proxy and origin and see whether the upstream sent a reset or a truncated response
- Subnet Calculator — confirm the upstream address is inside a range the proxy can actually route to
Related status codes
Codes people usually end up reading in the same session as 502.