502 Bad Gateway
Server error response defined by RFC 9110 §15.6.3.
Last updated July 27, 2026
Common causes at a glance
- Upstream process not running, so the proxy gets connection refused
- Upstream crashed or was OOM-killed part-way through the response
- Proxy speaking cleartext to a TLS port, or TLS to a cleartext port
- Response headers larger than the proxy buffer, such as an oversized Set-Cookie
Reproduce it
curl -sS -o /dev/null -w 'code=%{http_code} t=%{time_total}\n' https://www.example.com/api/healthWhat 502 Bad Gateway means
A 502 is always generated by an intermediary, which is why the page looks nothing like your application’s error output. Three distinct situations collapse into this one code, and they have different fixes.
Connection refused: nothing is listening on the upstream address. The process crashed, the container was rescheduled, the port in the proxy config does not match the port the app binds. Premature close: something accepted the request and then died before finishing the response — an out-of-memory kill, a worker timeout, a panic. Invalid response: the upstream is alive and speaking, but not HTTP. That is a cleartext service behind a proxy configured for TLS, a TLS service behind a proxy speaking cleartext, or response headers exceeding the proxy’s buffer.
What the spec says
RFC 9110 §15.6.3 is narrow: the server, while acting
as a gateway or proxy, received an invalid response from an inbound server it accessed.
The word doing the work is invalid. A merely slow upstream is
504, and an upstream that deliberately returns 500 is passed
through as 500. The spec gives no guidance on retries, so proxy behaviour is
implementation-defined: nginx decides via
proxy_next_upstream, which
by default retries only error and timeout conditions, and
only for requests it considers safe to repeat.
What actually causes it
The proxy error log tells you which of the three it was, and the
phrasing is worth learning. nginx writes connect() failed (111: Connection
refused) while connecting to upstream for a dead listener,
upstream prematurely closed connection while reading response header for a
mid-response death, and upstream sent invalid header or
no live upstreams for garbage and for every backend being marked down. A
502 only under load usually means the upstream is being killed by a memory limit. A 502
constant from the first request after a deploy is almost always a port, scheme, or
hostname mismatch in the proxy configuration.
How to debug it
Take the proxy out of the picture. Hit the upstream on its own address and see whether it answers HTTP at all:
curl -sS -D - -o /dev/null --max-time 5 http://10.0.1.24:8080/api/health
If that works and the public URL 502s, the problem is the proxy’s view of the upstream: wrong port, wrong scheme, DNS resolving to a stale pod IP, or health checks having ejected every backend. Check proxy_pass and proxy_buffer_size and the pool state in the upstream module. If the direct request also fails, read that service’s logs around the timestamp for a crash.
Working examples
curl
# 1. Public URL: confirm the 502 and note how fast it arrives.
curl -sS -o /dev/null -w 'code=%{http_code} t=%{time_total}\n' \
https://www.example.com/api/health
# 2. Straight at the upstream, bypassing the proxy entirely.
curl -sS -D - -o /dev/null --max-time 5 http://10.0.1.24:8080/api/health
# 3. Is anything even listening? A refused connection makes curl exit 7.
curl -sS --max-time 3 http://10.0.1.24:8080/ ; echo "exit=$?"
Python (requests)
import time
import requests
# A fast 502 usually means connection refused; a slow one means the upstream
# accepted the request and then died. Time it.
start = time.monotonic()
resp = requests.get("https://www.example.com/api/health", timeout=15)
print(resp.status_code, f"{time.monotonic() - start:.2f}s", resp.headers.get("Server"))
try:
direct = requests.get("http://10.0.1.24:8080/api/health", timeout=5)
print("upstream:", direct.status_code)
except requests.exceptions.ConnectionError as err:
print("upstream refused or reset:", err)
Node (fetch)
// Distinguish "proxy answered 502" from "upstream unreachable from here".
const t0 = performance.now();
const res = await fetch("https://www.example.com/api/health");
console.log(res.status, ((performance.now() - t0) / 1000).toFixed(2) + "s",
res.headers.get("server"));
try {
const direct = await fetch("http://10.0.1.24:8080/api/health",
{ signal: AbortSignal.timeout(5000) });
console.log("upstream:", direct.status);
} catch (err) {
console.error("upstream unreachable:", err.message); // ECONNREFUSED, timeout, ...
}Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.
Other 5xx codes
- 500 Internal Server Error
- 501 Not Implemented
- 503 Service Unavailable
- 504 Gateway Timeout
- 505 HTTP Version Not Supported
- 506 Variant Also Negotiates
- 507 Insufficient Storage
- 508 Loop Detected
- 510 Not Extended
- 511 Network Authentication Required
- 520 Web Server Returns an Unknown Error
- 521 Web Server Is Down
- 522 Connection Timed Out
- 523 Origin Is Unreachable
- 524 A Timeout Occurred
- 525 SSL Handshake Failed
- 526 Invalid SSL Certificate
- 530 Unable to Resolve Origin Hostname