nginx 502 Bad Gateway: Complete Troubleshooting Guide for All Upstream Failures
Fix nginx 502 Bad Gateway, 504, connection refused, crash, and OOM errors with step-by-step diagnosis commands. Restore your upstream in minutes.
- nginx 502 Bad Gateway means nginx received an invalid or no response from the upstream server (Node.js, PHP-FPM, Gunicorn, etc.) — the backend is down, misconfigured, or crashing mid-request
- nginx 504 Gateway Timeout differs from 502: the upstream is reachable but too slow; 'connection refused' in the error log means the upstream port or Unix socket is not listening at all
- nginx crashes (core dumps, OOM kills), 'too many connections', high CPU, and permission denied errors each require distinct OS-level fixes: worker limits, open-file descriptors, socket ownership, and upstream keepalive tuning
- Quick fix checklist: (1) confirm upstream process is running and on the correct port, (2) verify proxy_pass address/port, (3) read upstream application logs, (4) reload nginx after any config change with nginx -t && systemctl reload nginx, (5) increase timeouts or connection limits if the issue is load-related
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Restart the upstream process | 502 or connection refused; backend crashed or was never started | < 1 min | Low — brief downtime during restart |
| Correct proxy_pass address or port | 502 after deployment or config change; wrong upstream target | < 2 min | Low — requires nginx reload |
| Increase proxy_read_timeout / proxy_connect_timeout | 504 or intermittent 502 under load; upstream is alive but slow | < 2 min | Low — may mask slow backends long-term |
| Raise worker_rlimit_nofile and worker_connections | nginx 'too many open files' or 'worker_connections are not enough'; high-traffic sites | 5 min | Low — requires nginx reload |
| Add upstream keepalive pool | High CPU, connection churn, slow response under sustained load | 10–30 min | Medium — validate with load test |
| Fix Unix socket file permissions | permission denied in error log; nginx cannot connect to PHP-FPM or uWSGI socket | 5 min | Low — verify user/group match after change |
| Investigate OOM kill or segfault / core dump | nginx crashes randomly; OOM messages in dmesg; core files present | 30–60 min | Medium — may require kernel tuning or nginx upgrade |
Understanding nginx 502 Bad Gateway and Related Upstream Errors
When nginx returns 502 Bad Gateway, the server successfully received your HTTP request but could not obtain a valid response from the upstream server it was proxying to. This is one of several related failure modes:
- 502 Bad Gateway — upstream returned an invalid response or closed the connection unexpectedly
- 504 Gateway Timeout — upstream is alive but exceeded
proxy_read_timeoutbefore responding - 502 with 'connection refused' — nothing is listening on the upstream address/port
- nginx not starting at all — the nginx process itself failed at boot
Identifying which variant you face is the essential first diagnostic step.
Step 1: Read the Exact Error Message in the Log
Always begin with nginx's error log:
tail -n 100 /var/log/nginx/error.log
# For a specific virtual host:
tail -n 100 /var/log/nginx/mysite.error.log
Key phrases and their root causes:
| Log excerpt | Root cause |
|---|---|
connect() failed (111: Connection refused) |
Upstream process not running or wrong port |
recv() failed (104: Connection reset by peer) |
Upstream crashed mid-request |
upstream timed out (110: Connection timed out) |
Upstream too slow — results in 504, not 502 |
no live upstreams while connecting to upstream |
All servers in the upstream pool are down |
(13: Permission denied) |
nginx user cannot access the Unix socket |
worker_connections are not enough |
Raise worker_connections and worker_rlimit_nofile |
Step 2: Confirm the Upstream Process Is Running
For PHP-FPM:
systemctl status php8.2-fpm
ss -tlnp | grep 9000
# Or for a Unix socket:
ls -la /run/php/php8.2-fpm.sock
For Node.js, Gunicorn, or uWSGI:
systemctl status myapp
ps aux | grep -E 'node|gunicorn|uwsgi'
ss -tlnp | grep 3000 # adjust port to match your app
If the process is dead, restart it and watch the logs:
systemctl restart myapp
journalctl -u myapp -f
Step 3: Verify the proxy_pass Configuration
A mismatched port or address in the nginx config is a frequent cause of 502 after deployments. Example correct configuration:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000; # must match the app's listening port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Always test before reloading:
nginx -t && systemctl reload nginx
Step 4: Fix Timeout-Related 502 and 504 Errors
If the upstream is alive but slow (heavy database queries, external API calls), increase the timeout directives:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
}
For WebSocket applications or long-polling endpoints, values up to 300s or higher may be appropriate.
Step 5: Diagnose nginx Service Not Starting
If systemctl start nginx fails immediately, run:
nginx -t # find config syntax errors — output shows file and line
journalctl -u nginx -n 50 # full systemd journal for the unit
ss -tlnp | grep ':80\|:443' # another process already on the port?
Common causes of nginx failing to start:
- Port conflict: Apache or a previous nginx process holds port 80/443. Identify the owner with
ss -tlnp | grep ':80'and stop it. - Config syntax error:
nginx -twill print the exact file and line number. - Missing or expired SSL certificate: The path in
ssl_certificatedoes not exist or Let's Encrypt certificate has expired.
Step 6: Fix Permission Denied on Unix Sockets
When nginx proxies to a PHP-FPM or uWSGI Unix socket, the nginx worker user must have read/write access to the socket file:
ls -la /run/php/php8.2-fpm.sock
# Expected: srw-rw---- 1 www-data www-data ...
Ensure nginx runs as the same user or group that owns the socket:
# /etc/nginx/nginx.conf
user www-data;
Configure PHP-FPM to set the socket permissions explicitly:
; /etc/php/8.2/fpm/pool.d/www.conf
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Restart both PHP-FPM and nginx after changing ownership settings.
Step 7: Resolve nginx High CPU and Too Many Connections
nginx high CPU is typically caused by:
- Too few worker processes — set
worker_processes auto;to use all CPU cores automatically - Connection churn — the upstream connection is torn down and rebuilt for every request. Add keepalive to the upstream block:
upstream backend {
server 127.0.0.1:3000;
keepalive 64; # number of idle connections to keep open per worker
}
For 'worker_connections are not enough' warnings, increase limits in /etc/nginx/nginx.conf:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
Also raise the OS-level file descriptor limit:
echo 'nginx soft nofile 65535' >> /etc/security/limits.conf
echo 'nginx hard nofile 65535' >> /etc/security/limits.conf
sysctl -w fs.file-max=200000
echo 'fs.file-max = 200000' >> /etc/sysctl.conf
Step 8: Investigate nginx Crash, Core Dump, and Out-of-Memory Kills
If nginx workers die unexpectedly:
# Check OOM killer activity
dmesg | grep -i 'oom\|killed process'
journalctl -k | grep nginx
# Check for core dump files
find /var/crash /tmp /var/log -name 'core*' 2>/dev/null
# Check crash signals in journal
journalctl -u nginx --since '2 hours ago' | grep -iE 'signal|crash|segfault|coredump'
For OOM kills: reduce the number of worker processes, add system swap, or increase RAM. For segfaults (core dumps), check the nginx version first — upgrade to the latest stable release. If crashes persist, disable third-party modules one by one. Analyze a core file with:
gdb /usr/sbin/nginx /path/to/core
(gdb) bt full
Step 9: Fix nginx Slow Response
Slowness unrelated to upstream processing can have several nginx-side causes:
- DNS resolution latency — if
proxy_passuses a hostname, nginx resolves it at config load only. Use IP addresses directly or add aresolverdirective. - Buffering misconfiguration — for streaming or large file transfers:
proxy_buffering off;
# or for large API responses:
proxy_buffer_size 128k;
proxy_buffers 4 256k;
- Missing compression — enable gzip for text payloads:
gzip on;
gzip_min_length 1024;
gzip_types text/plain application/json application/javascript text/css;
- Missing sendfile / TCP tuning for static asset serving:
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
}
Frequently Asked Questions
#!/usr/bin/env bash
# nginx-diagnose.sh — rapid triage for 502/504/crash/slow/permission issues
# Run as root or with sudo
set -euo pipefail
echo '============================================================'
echo ' nginx diagnostic report'
echo ' date:' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
echo '============================================================'
echo ''
echo '--- nginx version ---'
nginx -v 2>&1
echo ''
echo '--- nginx service status ---'
if systemctl is-active --quiet nginx; then
echo 'nginx: ACTIVE'
else
echo 'nginx: NOT ACTIVE'
systemctl status nginx --no-pager -l | tail -20
fi
echo ''
echo '--- nginx config test ---'
nginx -t 2>&1
echo ''
echo '--- last 30 error log lines ---'
ERROR_LOG=$(nginx -T 2>/dev/null | grep 'error_log' | awk '{print $2}' | head -1)
ERROR_LOG=${ERROR_LOG:-/var/log/nginx/error.log}
if [ -f "$ERROR_LOG" ]; then
tail -30 "$ERROR_LOG"
else
echo "Error log not found at $ERROR_LOG"
fi
echo ''
echo '--- listening ports (nginx) ---'
ss -tlnp | grep nginx || echo 'No nginx sockets found'
echo ''
echo '--- common upstream processes ---'
for svc in php-fpm php8.2-fpm php8.1-fpm node gunicorn uvicorn uwsgi puma unicorn; do
if pgrep -x "$svc" > /dev/null 2>&1; then
echo "RUNNING : $svc (PID $(pgrep -x $svc | head -1))"
else
echo "ABSENT : $svc"
fi
done
echo ''
echo '--- upstream TCP ports in use ---'
ss -tlnp | grep -E ':3000|:5000|:8000|:8080|:9000' || echo 'None of the common upstream ports detected'
echo ''
echo '--- Unix sockets (PHP-FPM / uWSGI) ---'
find /run /tmp /var/run -name '*.sock' 2>/dev/null | while read -r sock; do
echo "$(ls -la $sock)"
done
echo ''
echo '--- OOM killer events (last 10) ---'
dmesg | grep -iE 'oom|killed process|out of memory' | tail -10 || echo 'No OOM events found'
echo ''
echo '--- nginx core dumps ---'
find /var/crash /tmp /var/log /core* /proc/*/cwd -name 'core*' 2>/dev/null | head -5 || echo 'No core dumps found'
echo ''
echo '--- open file descriptors (nginx master) ---'
NGINX_PID=$(pgrep -o nginx 2>/dev/null || true)
if [ -n "$NGINX_PID" ]; then
FD_COUNT=$(ls /proc/$NGINX_PID/fd 2>/dev/null | wc -l)
echo "nginx master PID: $NGINX_PID | open fds: $FD_COUNT"
else
echo 'nginx master process not found'
fi
echo ''
echo '--- connection summary ---'
ss -s | grep -E 'TCP|estab|closed|time-wait'
echo ''
echo '--- worker_connections configured ---'
grep -rn 'worker_connections\|worker_rlimit_nofile\|worker_processes' /etc/nginx/ 2>/dev/null || echo 'No nginx config found at /etc/nginx/'
echo ''
echo '--- OS file descriptor limit ---'
echo "fs.file-max = $(cat /proc/sys/fs/file-max)"
echo ''
echo '============================================================'
echo ' Diagnosis complete. Check errors above for root cause.'
echo '============================================================'Error Medic Editorial
Error Medic Editorial is a team of senior DevOps engineers, SREs, and infrastructure architects with combined experience across Linux production systems, Kubernetes clusters, cloud platforms, and high-traffic web stacks. Every guide is grounded in real incident post-mortems and cross-referenced against official upstream documentation.
Sources
- https://nginx.org/en/docs/http/ngx_http_proxy_module.html
- https://nginx.org/en/docs/http/ngx_http_upstream_module.html
- https://nginx.org/en/docs/ngx_core_module.html
- https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/
- https://stackoverflow.com/questions/5009324/node-js-nginx-what-now
- https://github.com/nginx/nginx/blob/master/src/event/ngx_event.c
- https://docs.nginx.com/nginx/admin-guide/monitoring/logging/