502 Bad Gateway means a proxy or gateway (nginx, Cloudflare, a load balancer, a tunnel) tried to reach the server behind it and got an invalid response or no response at all. The proxy itself is fine. The problem is the application it forwards to: it isn't running, it crashed mid-request, it's listening somewhere else, or it sent back something the proxy couldn't parse.
HTTP/1.1 502 Bad Gateway
Server: nginx
Content-Type: text/html
<html><head><title>502 Bad Gateway</title></head>
<body><center><h1>502 Bad Gateway</h1></center><hr><center>nginx</center></body></html>
Common causes
- The upstream app isn't running. The process crashed, failed to start after a deploy, or was killed by the OOM killer.
- Wrong host or port.
proxy_pass, the load balancer target or the tunnel points at a port nothing listens on, or the app binds to127.0.0.1inside a container so the proxy can't reach it. - The app closed the connection. A worker was killed mid-request (Gunicorn's worker timeout is 30 seconds by default), or the app's keep-alive timeout is shorter than the proxy's, so the proxy reuses a connection the app already closed.
- Scheme mismatch. The proxy speaks HTTPS to an app serving plain HTTP, or the reverse.
- Invalid response. Oversized headers, a malformed status line, or a broken TLS handshake between proxy and origin.
How to fix it as a visitor
Reload after a minute. A 502 is often a short window during a deploy or restart. If it persists, try another network or check the site's status page. It's a server-side problem, so there's little else you can do.
How to fix it as a developer or ops
Start with the proxy's error log. It almost always names the cause.
| Where | What you see | What it means |
|---|---|---|
| nginx | connect() failed (111: Connection refused) while connecting to upstream | Nothing listens at the proxy_pass address. Start the app or fix the port. |
| nginx | upstream prematurely closed connection while reading response header | The app crashed or was killed mid-request. Check app logs and worker timeouts. |
| nginx | no live upstreams while connecting to upstream | Every server in the upstream block is marked failed. |
| Cloudflare | 502 Bad Gateway or Error 520/521 | The origin refused, reset or returned an invalid response. 521 means it refused the connection. |
| AWS ALB | 502 in access logs, with target_status_code logged as a dash (no response) | The target closed the connection or sent a malformed response. |
Specific fixes:
- Confirm the app answers locally:
curl -v http://127.0.0.1:8000/from the proxy host. If that fails, the problem isn't the proxy. - Bind to the right interface. In Docker, the app must listen on
0.0.0.0, not127.0.0.1, for another container to reach it. - Line up keep-alive timeouts. Behind an AWS ALB (idle timeout 60 seconds by default), set the app's keep-alive timeout higher than the ALB's. Node.js defaults
server.keepAliveTimeoutto 5 seconds, a classic cause of intermittent ALB 502s. - Check memory and restarts.
dmesg | grep -i oom,kubectl describe pod, or your process manager's logs show whether the app keeps dying.
If the proxy waited for the app and gave up instead, that's a 504 Gateway Timeout, not a 502.
502 from a tunnel or local webhook forwarder
A 502 is the classic tunnel error. The tunnel reached your machine, but the local app it forwards to didn't answer. With relay connect --name my-app http://localhost:3000 or relay forward --bucket my-app http://localhost:3000/webhooks, a 502 or a timeout reported by the provider means the agent couldn't reach the destination. Check that:
- the dev server is actually running and listening on that port (see localhost:3000 for "refused to connect" fixes);
- the port in the command matches the one your app printed on startup;
- you used
http://for a plain local server, nothttps://.
502s on a webhook endpoint
When a provider gets a 502 from your webhook endpoint, it counts as a failed delivery. Most providers retry, but their windows vary from days to minutes, and some never retry automatically. A bad deploy that leaves the app down for an hour can silently lose events.
The fix is to make sure the event is stored somewhere before your app has to be up. Webhook Relay durable retries persist each webhook first, then retry with exponential backoff for up to 30 days until your endpoint returns 2xx. Because delivery is at least once, make your handler idempotent.
When to return 502 from your own service
Return 502 only when your service is itself acting as a gateway and the upstream it called returned something invalid. If your own code failed, that's a 500 Internal Server Error. If you're deliberately refusing traffic during maintenance or overload, use 503 Service Unavailable.
