# 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](/http/502) | "The app gave me garbage, or hung up" | The app crashed, isn't running, or dropped the connection |
| [503 Service Unavailable](/http/503) | "Not right now" | Overloaded, in maintenance, or no healthy backends |
| [504 Gateway Timeout](/http/504) | "The app took too long" | A slow query, a slow external call, or a stuck worker |

A [500 Internal Server Error](/http/500), 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_pass` points 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_size` and `proxy_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](/http/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](/http/202) 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](/http/520): the origin returned an empty or invalid response — roughly a 502.
- [521](/http/521): the origin refused the connection — the server is down, or a firewall rejects Cloudflare.
- [522](/http/522): the TCP connection to the origin timed out — often a firewall silently dropping Cloudflare's IP ranges.
- [524](/http/524): connected, but no response within 100 seconds — roughly a 504.

## A quick triage routine

1. Hit the app directly, bypassing the proxy. If it doesn't respond, the problem is the app — 502 territory.
2. Read the proxy's error log for the exact upstream failure.
3. Seeing 503s? Check health checks and capacity.
4. 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](/tools/web/http-status-checker). Every other status code is in the [HTTP status code reference](/http).
