devkult_
tools29converters34
home/blog/502-vs-503-vs-504
httpdebuggingdevops

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_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.

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

  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. Every other status code is in the HTTP status code reference.

More from the blog