nginx 502: upstream prematurely closed connection

Linux high nginxreverse-proxy502
2026/10/24 10:12:44 [error] 1201#1201: *57 upstream prematurely closed connection while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "GET /api/health HTTP/1.1", upstream: "http://127.0.0.1:8080/api/health"

What you see

The browser gets 502 Bad Gateway. error.log contains upstream prematurely closed connection while reading response header from upstream, and the upstream: field tells you which socket nginx tried.

Why it happens

nginx connected to the backend, sent the request, and the backend closed the socket without a response. The proxy did its job; the application did not.

Common causes, in the order worth checking:

  • the app crashed on that request (uncaught exception, OOM kill)
  • the app took longer than proxy_read_timeout (default 60s)
  • PHP-FPM workers are exhausted, or the pool listens on a different socket
  • the app binds to 127.0.0.1 while nginx proxies to a container or another host
  • an upstream keepalive mismatch closes idle connections that nginx tries to reuse

Fix

1. Confirm the backend is alive.

curl -sv http://127.0.0.1:8080/api/health

If this hangs or drops, the problem is the app, not nginx. Read its own log first.

2. Check the app log around the timestamp.

journalctl -u myapp --since "10:10" --no-pager
dmesg -T | grep -i -E 'killed process|oom'

An OOM kill shows up in dmesg, not in the app log.

3. Raise the read timeout only after you know the request time.

location /api/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_read_timeout 120s;
    proxy_connect_timeout 10s;
}
sudo nginx -t && sudo systemctl reload nginx

4. Check PHP-FPM or the worker pool, if that is the backend.

systemctl status php8.3-fpm --no-pager
tail -n 50 /var/log/php8.3-fpm.log

Look for pool www seems busy or server reached max_children.

5. Verify the socket path matches.

ss -ltnp | grep 8080
grep -r proxy_pass /etc/nginx/

A backend listening on 0.0.0.0:8080 is reachable at 127.0.0.1; one listening on a Unix socket is not.

6. If you use upstream keepalive, keep the timeouts aligned.

upstream app {
    server 127.0.0.1:8080;
    keepalive 32;
    keepalive_timeout 60s;
}

The backend’s idle timeout must be longer than nginx’s, or nginx will reuse a connection the backend just closed.

Verify

curl -I https://example.com/api/health
tail -f /var/log/nginx/error.log

A 200 with no new upstream prematurely closed lines means the loop is closed.