Troubleshooting

nginx 502 Bad Gateway: it is your upstream, not nginx

A 502 means nginx could not reach your app. Read the error log, then fix the four causes: upstream down, wrong socket, timeout, or socket permissions.

Published ~6 min read
On this page

A 502 Bad Gateway from nginx means nginx is working fine: it is your upstream that failed. nginx tried to hand the request to your application (PHP-FPM, a Node process, gunicorn, another server) and got nothing usable back. So you debug the thing behind nginx, not nginx itself. For the same status from Apache, a load balancer or a CDN, see the general 502 Bad Gateway guide.

Its neighbours look alike but point elsewhere. A 500 Internal Server Error means the backend did answer, with an error of its own, and a 503 Service Unavailable means something refused the request on purpose, such as a full PHP-FPM pool or a maintenance switch. A 404 Not Found is no failure at all: nginx or the app answered and found nothing at that path. If the site runs behind Cloudflare and the error page is Cloudflare’s own, read its code instead: error 520 means the answer from your server broke off, and error 522 that Cloudflare could not connect to it.

Always start with the error log

The log names the exact reason. Read it before touching config:

tail the error log
sudo tail -n 50 /var/log/nginx/error.log

The message points straight at the cause:

  • connect() failed (111: Connection refused): the upstream is not running.
  • no live upstreams: every backend nginx knows about is down.
  • upstream timed out: the backend is alive but too slow.
  • connect() to unix:/…​.sock failed: wrong socket path or permissions.

Cause 1: the upstream is not running

check + restart the backend
# pick the one you use
sudo systemctl status php8.3-fpm
sudo systemctl status your-node-app
sudo systemctl restart php8.3-fpm

Confirm it is actually listening where nginx expects. If nginx proxies to 127.0.0.1:3000, that port must show up here:

is the backend listening?
sudo ss -tlnp | grep -E ':3000|php|fpm'

Cause 2: wrong socket or port in the config

The fastcgi_pass or proxy_pass target must match what the backend binds. A PHP upgrade that changes the socket path (php8.2-fpm.sock → php8.3-fpm.sock) is a classic trigger:

check the mismatch
grep -R "fastcgi_pass\|proxy_pass" /etc/nginx/
ls /run/php/    # what socket actually exists?

Cause 3: the backend is too slow

If the log says timed out, the app works but exceeds nginx’s patience. Raise the timeout as a stopgap, but fix the slow code or add resources for a real cure:

raise proxy timeout
# in the location or server block
proxy_read_timeout 120s;
# then
sudo nginx -t && sudo systemctl reload nginx

Cause 4: socket permissions

When nginx and the backend run as different users, nginx may not be allowed to open the socket. Align the FPM pool’s listen.owner/listen.group with the nginx user (often www-data), then restart both.

Once the backend answers again, check the page from outside: the HTTP header checker requests it from our server, shows the status code of every redirect on the way and says when a CDN’s bot protection answered instead of your server.

Persistent 502s under traffic often mean the server is simply undersized. A Hi-CPU KVM plan gives PHP-FPM and Node the headroom to keep responses inside nginx’s timeout window.

Frequently asked

Is a 502 nginx's fault?

No. nginx is working: it tried to pass the request to your backend (PHP-FPM, Node, etc.) and got nothing usable. Debug the upstream, not nginx.

Where do I find the actual cause?

In /var/log/nginx/error.log. The message names it: 'connection refused' (upstream down), 'upstream timed out' (too slow), or a socket-path error.

502s appear only under load. Why?

Usually PHP-FPM running out of workers or the server running out of RAM. Check 'free -m' and the FPM pm.max_children setting before raising the timeout.

Related articles

All troubleshooting