# Fix Nginx 502 Bad Gateway When Backend Crashes Unexpectedly
The 502 Bad Gateway is one of the most common production errors in Nginx reverse proxy setups. Unlike 504 timeouts where the backend is slow, a 502 means the backend actively failed -- it crashed, refused the connection, or sent an invalid response.
The error log tells you exactly what went wrong:
2026/04/08 13:27:45 [error] 5678#5678: *45678 connect() failed (111: Connection refused) while connecting to upstream, client: 198.51.100.22, server: app.example.com, request: "GET /api/users/profile HTTP/1.1", upstream: "http://127.0.0.1:3000/api/users/profile"2026/04/08 13:28:01 [error] 5678#5678: *45680 upstream prematurely closed connection while reading response header from upstream, client: 198.51.100.232026/04/08 13:28:15 [error] 5678#5678: *45682 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 198.51.100.24Each of these has a different root cause.
Connection Refused (111)
The backend is not listening on the expected address and port. Check:
sudo systemctl status myapp
ss -tlnp | grep 3000
sudo journalctl -u myapp --since "5 minutes ago" --no-pagerCommon causes: the backend process crashed and has not restarted, the backend is starting up but not yet listening (race condition after deploy), or the backend binds to the wrong interface.
Fix with a systemd service that auto-restarts:
```ini [Unit] Description=My Application After=network.target
[Service] Type=simple User=www-data WorkingDirectory=/var/www/myapp ExecStart=/usr/bin/node server.js Restart=always RestartSec=3 StartLimitBurst=5 StartLimitIntervalSec=30
[Install] WantedBy=multi-user.target ```
The RestartSec=3 gives the application 3 seconds between crash and restart, preventing rapid restart loops.
Prematurely Closed Connection
The backend accepted the connection but closed it before sending a complete response. This often happens with OOM killer terminating the backend, unhandled exceptions causing process exit during request handling, or health check timeouts killing long-running requests.
Check if OOM killed your process:
sudo dmesg -T | grep -i "killed process"If OOM is the culprit, either reduce memory usage, add swap, or increase system memory:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfileConnection Reset by Peer (104)
The backend sent a TCP RST packet, usually because the backend process crashed mid-response, a firewall rule is resetting connections, or the backend's keepalive timeout is shorter than Nginx's.
Check keepalive timeout alignment:
```nginx upstream backend { server 127.0.0.1:3000; keepalive 16; keepalive_timeout 30s; }
server { location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_next_upstream error timeout http_502 http_503; proxy_next_upstream_tries 2; } } ```
The proxy_next_upstream directive automatically retries the request on a different backend server if the first one returns 502. This provides immediate failover without the client seeing an error.
Adding Upstream Health Checks
For critical services, add passive health checks:
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s backup;
}If the primary backend fails 3 times within 30 seconds, Nginx marks it as unavailable and routes traffic to the backup server. After 30 seconds, Nginx tries the primary again.
Graceful Error Pages
While fixing the root cause, serve a friendly error page:
server {
error_page 502 /502.html;
location = /502.html {
root /var/www/error-pages;
internal;
}
}This ensures users see a helpful message with a retry button instead of a generic browser error page.