HTTP 502 Bad Gateway - What It Means and How to Fix It
HTTP 502 Bad Gateway means a server acting as a gateway or proxy, such as nginx, a load balancer or a CDN, received an invalid response from the upstream server it forwarded the request to. The upstream app is usually down, crashed mid-request, or listening somewhere the proxy is not looking. The proxy's error log names the exact reason.
A 502 Bad Gateway means a server acting as a gateway or proxy received an invalid response from the inbound server it contacted while trying to fulfill the request. That is the RFC 9110 definition (section 15.6.3). In everyday terms: nginx, your load balancer or your CDN tried to reach your application, and what came back was a refused connection, a dropped connection or something that was not valid HTTP. The application is the usual suspect; the proxy is just the messenger.
Who returns a 502
A 502 is only ever produced by something sitting in front of another server:
- Reverse proxies such as nginx, Apache with
mod_proxy, HAProxy, Caddy or Traefik, in front of an app server like Gunicorn, uWSGI, Puma, PHP-FPM or a Node.js process. - Load balancers such as AWS Application Load Balancer, Google Cloud Load Balancing or Kubernetes ingress controllers.
- CDNs such as Cloudflare, Fastly or CloudFront when the origin returns something they cannot use.
When there are several layers (CDN, then load balancer, then nginx, then the app), the 502 can come from any of them. The Server response header and the style of the error page usually tell you which layer generated it.
curl -sS -D - -o /dev/null https://example.com/
A response with server: cloudflare and a Cloudflare-branded page came from Cloudflare. A plain page with "nginx" at the bottom came from your nginx.
Common causes
- The app process is not running. It crashed, was killed by the out-of-memory killer, or failed to start after a deploy. The proxy's connection is refused.
- Wrong upstream address. The app listens on port 8000 but nginx points to 8080, or a Unix socket path changed after a PHP version upgrade.
- The app crashed mid-response. A worker died while handling the request (a segfault, a worker timeout, a memory limit), so the proxy saw the connection close before a full response arrived.
- Response headers too large. Large cookies or many
Set-Cookieheaders can exceed nginx'sproxy_buffer_size, and nginx treats that as an invalid upstream response. - Protocol mismatch. The proxy speaks plain HTTP to an upstream expecting HTTPS, or vice versa, or the upstream does not speak HTTP at all on that port.
- All backends marked unhealthy. A load balancer has no healthy targets, or nginx has marked every server in an
upstreamblock as failed. - Firewall or security group changes that block the proxy from reaching the app.
How to diagnose a 502
Read the proxy's error log first
On nginx, the error log usually states the cause in one line:
sudo tail -n 50 /var/log/nginx/error.log
What the common lines mean:
| nginx error log text | Likely cause |
|---|---|
connect() failed (111: Connection refused) while connecting to upstream |
Nothing is listening on the upstream port. App down or wrong port. |
connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory) |
Socket path is wrong or PHP-FPM is not running. |
connect() to unix:... failed (13: Permission denied) |
nginx's user cannot access the socket. Check socket owner and mode. |
upstream prematurely closed connection while reading response header from upstream |
The app accepted the request and then died or closed the connection. |
upstream sent too big header while reading response header from upstream |
Response headers exceed the proxy buffer. |
no live upstreams while connecting to upstream |
Every server in the upstream group is marked as failed. |
recv() failed (104: Connection reset by peer) |
The upstream reset the connection, often a crash or a protocol mismatch. |
Check the upstream directly
Bypass the proxy and talk to the app on the server itself:
sudo systemctl status myapp
sudo ss -ltnp | grep 8000 # is anything listening on the port?
curl -sS -i http://127.0.0.1:8000/ # does the app answer locally?
If the local request also fails, the problem is the app. If it succeeds, compare the address and protocol with the proxy_pass or fastcgi_pass line in your nginx config.
Look at the app's own logs
sudo journalctl -u myapp --since "15 minutes ago"
sudo dmesg -T | grep -i "killed process" # out-of-memory kills
For Gunicorn, a line such as WORKER TIMEOUT means the worker was killed by Gunicorn's own timeout (30 seconds by default) and the proxy received a closed connection. For PHP-FPM, server reached pm.max_children setting means requests are queuing for workers.
How to fix it
- Start or restart the app, then find out why it stopped. Set
Restart=on-failurein the systemd unit so a crash does not leave you down until someone notices. - Correct the upstream address. Make
proxy_passorfastcgi_passmatch what the app actually listens on, then runsudo nginx -t && sudo systemctl reload nginx. - Fix socket permissions. Set
listen.owner,listen.groupandlisten.modein the PHP-FPM pool so the nginx user can read the socket. - Raise buffers for large headers only if headers are legitimately large:
proxy_buffer_size 16k;
proxy_buffers 8 16k;
- Give workers memory and time. Reduce memory per request, increase the worker's own timeout only if the slow requests are expected, and move long jobs to a background queue.
- Make deploys graceful. Use a rolling restart, a reload signal your app server supports, or start the new instance and switch traffic only when it passes a health check.
- Fix health checks. If a load balancer marks every target unhealthy, confirm the health check path returns 200 and is not behind authentication.
Cloudflare 52x codes
Cloudflare uses its own non-standard codes for origin problems, which are distinct from 502:
- 520 – the origin returned an empty, unknown or unexpected response.
- 521 – the origin refused the connection.
- 522 – Cloudflare could not complete a TCP connection to the origin in time.
- 523 – the origin is unreachable, for example because of a DNS or routing problem.
- 524 – the connection was made but the origin did not send a response in time.
- 525 / 526 – the TLS handshake with the origin failed, or the origin's certificate is invalid.
Cloudflare also returns a standard 502 in some cases. Either way, the fix is usually at your origin, and the diagnosis steps above still apply.
Getting alerted on 502s
A 502 often comes and goes as a process crashes and restarts, which makes it easy to miss. An external HTTP monitor that checks your site every minute or few minutes records each occurrence and alerts you when it persists. With PingRobot, HTTP(S) monitors treat a 502 as a failure, re-check it from a second region to rule out a network blip, then alert you by email. A TCP port monitor on the app's port can also tell you whether the upstream itself is reachable, if that port is exposed to the monitor.
If the site returns a timeout instead, read the 504 Gateway Timeout guide; for application crashes that return 500, see HTTP 500 Internal Server Error.
Start monitoring free — 10 monitors, no credit card.
Frequently asked questions
Is a 502 error a problem with my browser or internet connection?
Almost never. A 502 is generated by a server between you and the application. Clearing the browser cache rarely helps; the fix is on the server side.
What is the difference between 502 and 504?
Both come from a proxy. A 502 means the upstream answered with something invalid or closed the connection. A 504 means the upstream did not answer within the proxy's timeout.
Why do I get 502 errors only during deploys?
During a restart the app process is briefly not listening, so the proxy's connection is refused. Use a rolling or graceful reload so a new process is ready before the old one stops.
Is a Cloudflare 520 or 521 the same as a 502?
No. Codes 520 to 526 are Cloudflare-specific and not defined in RFC 9110. They describe problems between Cloudflare and your origin, such as the origin refusing connections (521) or returning an empty or unexpected response (520).