502 vs 503 vs 504: What Each Gateway Error Means
When a site goes down you usually see one of three codes. They're easy to confuse, but each tells you something specific about where the failure is — which is most of the work of fixing it.
Most production setups have at least two layers: a proxy that receives the request (nginx, a load balancer, a CDN, an API gateway), and the application server behind it. The three codes describe different things going wrong between them.
| Code | In plain English | Usually means |
|---|---|---|
| 502 Bad Gateway | "The app gave me garbage, or hung up" | The app crashed, isn't running, or dropped the connection |
| 503 Service Unavailable | "Not right now" | Overloaded, in maintenance, or no healthy backends |
| 504 Gateway Timeout | "The app took too long" | A slow query, a slow external call, or a stuck worker |
A 500 Internal Server Error, by contrast, comes from the application itself: it ran, and threw.
502 Bad Gateway: the app answered badly
The proxy connected — or tried to — and got something unusable back: a refused connection, a reset mid-response, bytes that weren't valid HTTP. The proxy is healthy; whatever is behind it isn't.
Look first at the proxy's error log. nginx says exactly what went wrong:
connect() failed (111: Connection refused) while connecting to upstream
upstream prematurely closed connection while reading response header from upstream
upstream sent too big header while reading response header from upstream
- Connection refused: the app isn't running, or it listens on a different port or socket from the one
proxy_passpoints at. - Prematurely closed: the app died mid-request. Check its logs, and the kernel log for out-of-memory kills (
dmesg | grep -i kill). - Too big header: raise
proxy_buffer_sizeandproxy_buffers— large cookies are the usual reason.
Intermittent 502s behind a cloud load balancer often come from a keep-alive race: the app closes an idle connection at the moment the balancer reuses it. Make the app's keep-alive timeout longer than the balancer's idle timeout — in Node.js, set server.keepAliveTimeout above the AWS ALB default of 60 seconds.
503 Service Unavailable: not serving, on purpose or by necessity
503 means the server knows it can't handle the request right now. Sometimes that's deliberate — a maintenance page. Sometimes it's a capacity limit: a full connection pool, a worker queue at its maximum, or a load balancer with no healthy targets.
Look first at health checks and capacity. If every target behind a load balancer fails its health check, the balancer answers 503 for everything, even if the app could serve requests fine. A health check pointed at a path that now redirects or requires a login is a classic cause.
For planned maintenance, 503 is the right status. Send Retry-After, and search engines will pause and come back — instead of indexing a "back soon" page served with 200.
504 Gateway Timeout: the app was too slow
The proxy reached the app and sent the request, but no response arrived before its timeout. nginx's proxy_read_timeout and AWS ALB's idle timeout both default to 60 seconds; Cloudflare waits 100 seconds and then shows its own 524.
Look first at what the request was doing. Slow database queries and slow third-party API calls cause most 504s. Add timing logs around each, or check your APM for the slowest endpoints.
Two things to keep in mind:
- The work may have finished anyway. The backend often completes after the proxy stops waiting, so retrying a payment or an order after a 504 can do it twice. Make those operations idempotent.
- Raising the timeout is a stopgap. If a request legitimately takes minutes, move it to a background job and return 202 Accepted with a URL to poll.
Behind Cloudflare: 520 to 524
Cloudflare uses its own codes so you can tell its edge apart from your origin:
- 520: the origin returned an empty or invalid response — roughly a 502.
- 521: the origin refused the connection — the server is down, or a firewall rejects Cloudflare.
- 522: the TCP connection to the origin timed out — often a firewall silently dropping Cloudflare's IP ranges.
- 524: connected, but no response within 100 seconds — roughly a 504.
A quick triage routine
- Hit the app directly, bypassing the proxy. If it doesn't respond, the problem is the app — 502 territory.
- Read the proxy's error log for the exact upstream failure.
- Seeing 503s? Check health checks and capacity.
- Seeing 504s? Find the slow request — and don't blindly retry non-idempotent ones.
To see what a set of URLs returns right now, paste them into the HTTP Status Checker. Every other status code is in the HTTP status code reference.