502 Bad Gateway: what it means and how to fix it

  • http-errors

A 502 is good news wearing a scary costume: something in your stack is alive and talking — that’s who sent the 502 — but the thing behind it returned garbage or nothing. Your job is to find the first hop that stops answering.

What does 502 Bad Gateway actually mean?

A server acting as a gateway — nginx, Apache, a load balancer, a CDN — forwarded your request upstream and got an invalid response back: a connection refused, a reset, a malformed reply, or silence. The proxy is fine. The upstream is not.

Which layer is sending the 502?

Read the error page itself first. “nginx” branding means your nginx reached a dead upstream. A Cloudflare-styled 502 means Cloudflare couldn’t get a sane answer from your origin — which may itself be an nginx with its own dead upstream. Then test hops directly:

# Through the front door
curl -sI https://example.com

# Straight to your origin, bypassing the CDN
curl -sI --resolve example.com:443:203.0.113.7 https://example.com

# From the origin box: is the app process even listening?
curl -sI http://127.0.0.1:3000

Wherever the answer changes from 502 to something else, you’ve crossed the broken link.

Why do upstreams stop answering?

In rough order of likelihood:

  • The app process crashed or was never restarted after a deploy. systemctl status myapp or docker ps settles it in seconds.
  • The app is up but overwhelmed — connections accepted, replies too slow or reset. Often shows up as a mix of 502s and 504s.
  • Port or socket mismatch — the proxy points at :3000, the app now listens on :8080, or a unix socket path changed.
  • The upstream’s TLS is broken and the proxy refuses the handshake.

Your proxy’s error log names the culprit precisely:

tail -f /var/log/nginx/error.log
# "connect() failed (111: Connection refused) while connecting to upstream"

Connection refused means nothing is listening. Timeouts point to 504 territory.

How do I stop the next 502 from being a surprise?

A 502 is exactly the failure your own browser hides from you — the site worked when you deployed, died an hour later, and the first report came from a user. An external check catches it from outside your network: YoPingMe requests your site from Frankfurt, Virginia, and Singapore and pages you by email, webhook, or Slack only when all three agree it’s down. Ten monitors are free, and the first check runs while you sign up.