ByteScope

502

How to fix HTTP 502 Bad Gateway

5xx server errorIETF standardRFC 9110

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.

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
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 withHow to tell them apart
504Both 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.
500500 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.
503503 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.
521521 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.
nginxnginx 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.

Related status codes

Codes people usually end up reading in the same session as 502.

Frequently asked questions

How do I find out which server produced this status code?

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.

Does the reason phrase after the number matter?

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.

Should I retry a request that failed with this code?

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.

Can I make my server return a different status code?

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.