Skip to main content
DevOpsintermediate

Fix "502 Bad Gateway" in Nginx — Read the Error Log First

Fix nginx 502 Bad Gateway. Decode the upstream errors in error.log — connection refused, permission denied on the PHP-FPM socket, and upstream sent too big header — and apply the right fix for each.

9 min readUpdated August 2026

Nginx returns 502 Bad Gateway when it hands a request to a backend and gets back nothing usable. Nginx itself is working fine — the upstream is the problem, and nginx has already written down exactly which one.

502 Bad Gateway
nginx/1.24.0

Step 1: Read the Error Log

Do this before changing anything. Every 502 writes a line to the nginx error log naming the upstream and the underlying errno:

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

You are looking for a line like one of these:

connect() failed (111: Connection refused) while connecting to upstream, client: 203.0.113.9,
  server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:3000/"

connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied)
  while connecting to upstream

upstream sent too big header while reading response header from upstream

recv() failed (104: Connection reset by peer) while reading response header from upstream

no live upstreams while connecting to upstream

The number in parentheses is the actual fault. Match it below. If you are not sure where your logs live, see Where Are Nginx Logs Stored? — a custom error_log directive may have moved them.


Cause 1: (111: Connection refused) — Nothing Is Listening

The most common 502 by a wide margin. Nginx connected to an address where no process is accepting.

Check whether the backend is running:

sudo systemctl status php8.3-fpm     # or your app's unit
sudo ss -ltnp                        # what is actually listening

ss -ltnp is the decisive command — it lists every listening TCP socket with the owning process. Compare that against your proxy_pass:

location / {
    proxy_pass http://127.0.0.1:3000;
}

If nginx points at port 3000 and ss shows your app on 8000, that is your bug. If nothing is listening at all, start the backend:

sudo systemctl start php8.3-fpm
sudo systemctl enable php8.3-fpm     # so it survives reboot

A subtle variant: the backend binds to 127.0.0.1 but nginx connects to a different address, or the app listens only on IPv6 while nginx resolves localhost to IPv4. Use an explicit 127.0.0.1 on both sides rather than localhost to eliminate the ambiguity.

If the backend keeps dying, read its own log rather than nginx's. A process that starts, serves a few requests, and exits is a crash, not a configuration error.


Cause 2: (13: Permission denied) — Socket Permissions

Seen almost exclusively with PHP-FPM over a Unix socket. The socket exists but the nginx worker user cannot open it.

In your PHP-FPM pool file (/etc/php/8.3/fpm/pool.d/www.conf on Debian/Ubuntu):

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

Set listen.owner and listen.group to the user nginx runs as — www-data on Debian/Ubuntu, nginx on RHEL, Rocky, and AlmaLinux. Confirm which with:

ps -o user= -C nginx | sort -u

Then restart PHP-FPM (not nginx):

sudo systemctl restart php8.3-fpm
ls -l /run/php/php8.3-fpm.sock

Permissions on the parent directory matter too — nginx needs execute (x) on every directory in the path to reach the socket.


Advertisement

Cause 3: upstream sent too big header — Buffers Too Small

The backend returned response headers larger than nginx's buffer. Large Set-Cookie values, long JWTs, and verbose auth headers all trigger it. The defaults are small:

DirectiveDefaultContext
proxy_buffer_size4k or 8khttp, server, location
proxy_buffers8 4k or 8 8khttp, server, location
proxy_busy_buffers_size8k or 16khttp, server, location

Raise them for a proxied backend:

proxy_buffer_size   16k;
proxy_buffers       4 16k;
proxy_busy_buffers_size 32k;

For PHP-FPM, the equivalent directives carry the fastcgi_ prefix:

fastcgi_buffer_size 16k;
fastcgi_buffers     4 16k;

Note that proxy_buffer_size covers the header only — that is why the error mentions "response header" specifically.


Cause 4: (104: Connection reset by peer) — The Backend Died Mid-Request

The upstream accepted the connection and then vanished. That is a crash, an OOM kill, or a worker limit.

dmesg -T | grep -i -E 'killed process|out of memory'

For PHP-FPM, check its log for the tell-tale saturation message:

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

server reached pm.max_children setting means you are out of workers — raise pm.max_children, but only if the box has the RAM for it. Each child consumes memory, and over-provisioning turns a 502 into an OOM kill.


Cause 5: SELinux (RHEL, Rocky, AlmaLinux)

SELinux blocks nginx from making outbound network connections by default, which looks like a connection failure with no obvious cause.

getenforce
sudo ausearch -m avc -ts recent | grep nginx

If you see AVC denials, allow the connection:

sudo setsebool -P httpd_can_network_connect 1

The -P makes it persist across reboots. Do not disable SELinux wholesale to fix one boolean.


502 vs 504: Not the Same Problem

CodeMeaningGoverning directive
502 Bad GatewayUpstream refused, reset, or sent a malformed response
504 Gateway TimeoutUpstream accepted the connection but did not answer in timeproxy_read_timeout (default 60s)

If you are getting 504s, raising buffers will not help — the backend is slow, and the honest fix is making it faster rather than raising proxy_read_timeout until the symptom disappears.


Verify the Fix

sudo nginx -t                          # config syntax
sudo systemctl reload nginx            # apply without dropping connections
curl -I http://127.0.0.1/              # expect 200, not 502
sudo tail -f /var/log/nginx/error.log  # watch for new upstream errors

Always run nginx -t before reloading. A config with a typo will fail to reload and leave the old config running — better than nginx refusing to start, but you will be debugging a fix that never applied.

Prevention

  • Enable the backend at boot with systemctl enable. A large share of "502 after reboot" is a backend that was never enabled.
  • Monitor the upstream, not just nginx. A health check that only hits nginx reports green while every request 502s.
  • Watch memory. OOM kills are the most common cause of intermittent 502s on small servers.
  • Alert on error.log. These errors are logged the instant they happen — see Where Are Linux System Logs Stored? for wiring them into a central log pipeline.

Frequently Asked Questions

Find answers to common questions

Nginx tried to hand your request to a backend — PHP-FPM, Node, Gunicorn, another server — and got an unusable answer or no answer at all. The 502 is nginx reporting that the upstream failed, not that nginx failed. The real reason is one line in /var/log/nginx/error.log.

Run 'sudo tail -n 50 /var/log/nginx/error.log'. Every 502 writes a line naming the upstream and the underlying errno, such as '(111: Connection refused)' or '(13: Permission denied)'. Without that line you are guessing; with it the fix is usually one step.

Nothing is listening on the address nginx tried. Either the backend process is stopped, it crashed, or proxy_pass points at the wrong port. Check the service with systemctl status and confirm what is actually listening using 'sudo ss -ltnp'.

The nginx worker user cannot open the Unix socket. In the PHP-FPM pool config set listen.owner and listen.group to the nginx user (www-data on Debian, nginx on RHEL) and listen.mode to 0660, then restart PHP-FPM. Directory permissions on the socket's parent folder matter too.

The backend returned response headers larger than nginx's buffer — usually a very large Set-Cookie or a long JWT. Raise proxy_buffer_size and proxy_buffers, or the fastcgi_ equivalents for PHP-FPM. The default proxy_buffer_size is only 4k or 8k.

502 means the upstream gave a bad or no response — refused, reset, or malformed. 504 means the upstream accepted the connection but did not answer in time, governed by proxy_read_timeout, which defaults to 60 seconds. A slow query gives 504; a dead process gives 502.

Yes, on RHEL, Rocky, and AlmaLinux. SELinux blocks nginx from making outbound network connections by default, producing connection-refused style failures. Run 'sudo setsebool -P httpd_can_network_connect 1' and check 'sudo ausearch -m avc -ts recent' for denials.

Almost never, because nginx is not the broken component. Restarting the upstream — PHP-FPM, your Node process, Gunicorn — fixes it far more often. If restarting the backend fixes it temporarily and it returns, the backend is crashing and its own log will say why.

Intermittent 502s usually mean the backend is running out of workers under load, crashing and restarting, or being killed by the OOM killer. Check the backend's own log and 'dmesg -T | grep -i oom'. For PHP-FPM, look for 'server reached pm.max_children setting' in its log.

Yours. 5xx codes are server-side by definition, and the visitor cannot do anything about it. Nothing about the request caused it, so retrying from a different browser or clearing a cache will not help.