502 Bad Gateway means Nginx is working, but the upstream it forwards requests to — PHP-FPM, a Node.js or Python app, a Docker container — failed to give a valid response. Nginx itself is rarely the problem; the error log tells you which upstream failed and why.

Step 1: read the Nginx error log

sudo tail -n 30 /var/log/nginx/error.log

(Or the site-specific error log configured in your server block.) The message identifies the cause:

Error log message Meaning
connect() failed (111: Connection refused) while connecting to upstream Nothing listens on that address/port — app down or wrong port
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) Wrong socket path, or PHP-FPM not running
connect() to unix:… failed (13: Permission denied) Nginx can’t access the socket (permissions or SELinux)
upstream prematurely closed connection while reading response header The app crashed or killed the request mid-way
upstream sent too big header while reading response header Response headers exceed Nginx’s buffers
no live upstreams while connecting to upstream All servers in an upstream block marked failed

Cause 1: the upstream isn’t running

For PHP:

systemctl status php8.3-fpm
sudo systemctl restart php8.3-fpm

For an app managed by systemd, PM2 or Docker:

systemctl status myapp
pm2 ls
docker ps -a

If it keeps crashing, read its logs (journalctl -u myapp, pm2 logs, docker logs). A frequent root cause on small servers is the out-of-memory killer:

sudo journalctl -k | grep -i "killed process"

Cause 2: wrong address, port or socket

Compare what Nginx expects with what’s actually listening:

grep -rn "proxy_pass\|fastcgi_pass" /etc/nginx/sites-enabled/
sudo ss -tlnp            # TCP listeners
ls -l /run/php/          # PHP-FPM sockets

Common mismatches:

  • After upgrading PHP, the socket name changes (php8.1-fpm.sock → php8.3-fpm.sock) but the Nginx config still points to the old one.
  • The app listens on 127.0.0.1:3000 but Nginx proxies to localhost:3000, which may resolve to IPv6 ::1 first — use 127.0.0.1 explicitly.
  • The app binds to a different port than configured (check its PORT environment variable).

Cause 3: socket permissions

The PHP-FPM pool config (/etc/php/8.3/fpm/pool.d/www.conf) defines who may use the socket:

listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Nginx must run as a user in that group (user www-data; in nginx.conf on Debian/Ubuntu; nginx on RHEL-family systems — adjust listen.owner/listen.group there). Restart PHP-FPM after changes.

Cause 4: SELinux (AlmaLinux, Rocky)

SELinux blocks Nginx from connecting to network ports by default. If the audit log shows denials:

sudo ausearch -m avc -ts recent | grep nginx
sudo setsebool -P httpd_can_network_connect 1

Cause 5: Docker upstreams

  • Nginx on the host proxying to a container: the container must publish the port (-p 127.0.0.1:8080:80) and Nginx must use the published port.
  • Nginx in a container: 127.0.0.1 means the Nginx container itself. Put both on the same Docker network and use the service name: proxy_pass http://app:3000;.
  • After a container is recreated, its IP changes. Nginx resolves hostnames at startup; if you proxy to a container name, reload Nginx after recreating, or use a resolver with variables.

See Docker networking.

Cause 6: the app is too slow or overloaded

If PHP-FPM has used all its workers, new requests queue and may fail:

sudo grep -i "max_children" /var/log/php8.3-fpm.log

WARNING: [pool www] server reached pm.max_children setting means you need more workers (if RAM allows), faster code, or caching. See PHP-FPM tuning.

Slow responses usually produce 504 Gateway Timeout rather than 502 — see Nginx 504 — but a PHP max_execution_time or request_terminate_timeout that kills a request produces 502.

Cause 7: header too big

Apps that set large cookies or many headers trigger upstream sent too big header. Increase the buffers:

# for proxy_pass
proxy_buffer_size 16k;
proxy_buffers 8 16k;

# for fastcgi_pass (PHP)
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;

Test the upstream directly

Bypass Nginx to see whether the upstream answers:

curl -i http://127.0.0.1:3000/

For PHP-FPM over a socket, use cgi-fcgi (package libfcgi-bin) or simply create a test PHP file and request it through Nginx while watching the PHP-FPM log.

Checklist

  1. Read the Nginx error log.
  2. Is the upstream running? Check its own logs.
  3. Do address/port/socket in Nginx match what’s listening?
  4. Socket permissions and SELinux OK?
  5. Docker: right network, right port?
  6. Worker exhaustion or memory kills?
  7. Header buffer sizes?

Related: Nginx 504 Gateway Timeout, Nginx 403 Forbidden, and the Nginx reverse proxy guide.

  • Nginx
  • Troubleshooting
  • PHP-FPM