nginx 502: upstream prematurely closed connection
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.1while 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.