502
A proxy in front of the application answered on its behalf, because the application's own answer was missing, truncated or not valid HTTP.
The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.
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.
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.
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.
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.
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.
| 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. |
All of these run in your browser — nothing is uploaded.
Codes people usually end up reading in the same session as 502.
Read the response headers rather than the page. curl -sS -o /dev/null -D - https://example.com/path prints them without the body, and server, via and cf-ray between them name the layer that answered. A cf-ray value means Cloudflare handled the response and is the id their support will ask for. To take the edge out of the picture entirely, repeat the request 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 — if the answer changes, the edge and the origin disagree.
No, and you should never branch on it. RFC 9112 tells clients to ignore the reason phrase, servers are free to change it, and HTTP/2 and HTTP/3 do not carry one at all — so 404 Not Found over HTTP/1.1 arrives as a bare 404 over HTTP/2. The phrases on this site are the registered ones because they are what people search for and what appears in an HTTP/1.1 log, not because any software depends on them.
Retry 5xx and 429; do not retry 4xx, because nothing about the request will be different next time. If the response carries Retry-After, honour it — RFC 9110 defines it for exactly this, and it may be either a number of seconds or a date. Otherwise use exponential backoff with jitter and a hard cap, and only for idempotent methods: a retried POST can charge a card twice. A retry storm against a server that is already failing is how a brief incident becomes a long one.
Yes, and the only rule that matters is that the code has to be true. Anything that reads your responses automatically — search engines, monitoring, caches, client retry logic — makes decisions from the number and never from the page. Serving an error page with 200 hides the failure from your own alerting; serving 200 for a missing page gets the error indexed as content; returning 500 for a request that was simply malformed sends whoever is on call to the wrong half of the stack.
Still stuck on 502? browse every status code in the reference — or go back up to the block written for your side of the stack.