How to Fix nginx 502 Bad Gateway: Complete Troubleshooting Guide
Fix nginx 502 Bad Gateway fast. Diagnose upstream crashes, PHP-FPM socket mismatches, proxy timeouts, and SSL handshake failures with real commands.
- The most common root cause is the upstream backend (PHP-FPM, Node.js, uWSGI, Gunicorn) has crashed or is not listening on the configured port or socket path
- A mismatch between the nginx upstream configuration and the actual socket/port the backend uses causes immediate connection refused errors
- Proxy timeout values (proxy_read_timeout, proxy_connect_timeout) that are too low will trigger a 502 before a slow backend finishes responding
- Unix socket permission errors prevent nginx workers from connecting even when the socket file exists
- Quick fix checklist: read /var/log/nginx/error.log for the exact upstream error, verify the backend process is running with systemctl status or ss -tlnp, correct any socket/port mismatch, then run nginx -t and systemctl reload nginx
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Restart upstream service (systemctl restart) | Backend process has crashed or stopped responding entirely | < 1 min | Low — brief downstream interruption |
| Correct socket or port in nginx upstream block | Error log shows 'No such file or directory' or wrong port in upstream address | 5 min | Low — requires nginx reload |
| Fix Unix socket ownership/permissions | Error log shows 'connect() failed (13: Permission denied)' | 5 min | Low — requires PHP-FPM and nginx reload |
| Increase proxy_read_timeout / proxy_connect_timeout | Backend is running but processing is slow; 502 appears under load or for heavy queries | 5 min | Low — may mask slow backend bugs |
| Configure proxy_ssl_verify and TLS settings | Proxying to an HTTPS upstream; error log shows SSL_do_handshake() failed | 15 min | Medium — disabling verification reduces security on untrusted networks |
| Add proxy_next_upstream retry logic | Multiple upstream workers available; need automatic failover on 502 | 10 min | Low — requires testing failover behavior |
Understanding the nginx 502 Bad Gateway Error
An nginx 502 Bad Gateway error means nginx successfully received your request but could not get a valid response from the upstream server it was supposed to proxy the request to. Nginx itself is working — the problem is always in the communication between nginx and the backend service (PHP-FPM, Node.js, Python/Gunicorn, Ruby/Puma, etc.).
This distinguishes 502 from related errors:
- 503 Service Unavailable: nginx's upstream pool is full (all workers busy) or explicitly marked down
- 504 Gateway Timeout: the upstream connected but took too long to respond
- 502 Bad Gateway: the upstream refused the connection, crashed mid-response, or returned malformed HTTP
In your nginx error log (/var/log/nginx/error.log), a 502 produces messages like:
2024/03/15 10:23:45 [error] 1234#1234: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 10.0.0.1, server: example.com, request: "GET /api/users HTTP/1.1", upstream: "http://127.0.0.1:9000/api/users"
2024/03/15 10:23:45 [error] 1234#1234: *2 connect() to unix:/run/php/php8.1-fpm.sock failed (2: No such file or directory) while connecting to upstream
2024/03/15 10:23:45 [error] 1234#1234: *3 recv() failed (104: Connection reset by peer) while reading response header from upstream
2024/03/15 10:23:45 [error] 1234#1234: *4 SSL_do_handshake() failed (SSL: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed) while SSL handshaking to upstream
2024/03/15 10:23:45 [error] 1234#1234: *5 upstream sent invalid header while reading response header from upstream
The exact error message in the log is your diagnostic shortcut — each message maps directly to a specific fix.
Step 1: Read the Error Log First
Never guess. Always start here:
# Show the 50 most recent nginx errors
tail -50 /var/log/nginx/error.log
# Filter specifically for upstream errors (the 502 source)
grep 'upstream' /var/log/nginx/error.log | tail -30
# If you have multiple virtual hosts, check site-specific logs
tail -50 /var/log/nginx/mysite.error.log
# For systemd-managed nginx, also check the journal
journalctl -u nginx -n 100 --no-pager
Use this table to map what you see to the cause:
| Log Snippet | Root Cause |
|---|---|
connect() failed (111: Connection refused) |
Backend not listening on configured address/port |
No such file or directory (socket path) |
Unix socket file does not exist — service is down |
connect() failed (13: Permission denied) |
nginx worker lacks permission to access the socket |
recv() failed (104: Connection reset by peer) |
Backend process crashed while handling the request |
SSL_do_handshake() failed |
TLS certificate mismatch or unsupported protocol to upstream |
upstream sent invalid header |
Backend returned non-HTTP data (app crash, wrong port) |
connect() failed (110: Connection timed out) |
Network/firewall blocking nginx→backend, or backend overloaded |
Step 2: Verify the Upstream Service Is Running
Once the error log shows the upstream address (e.g., 127.0.0.1:9000 or /run/php/php8.1-fpm.sock), confirm whether anything is actually listening there.
PHP-FPM backends:
# Check service status
systemctl status php8.1-fpm
# Confirm the socket file exists
ls -la /run/php/php8.1-fpm.sock
# Confirm which address PHP-FPM is listening on
grep -E '^listen' /etc/php/8.1/fpm/pool.d/www.conf
Node.js / Python / Go backends:
# List all TCP listeners — find your expected port
ss -tlnp | grep ':3000\|:8000\|:8080'
# Check process manager status
systemctl status myapp
journalctl -u myapp -n 50 --no-pager
# PM2 managed processes
pm2 status
pm2 logs myapp --lines 50
Docker or containerized backends:
# Verify the container is running and its port bindings
docker ps --filter name=myapp
# Check container logs for startup errors
docker logs myapp --tail 50
# Confirm which host port the container port is mapped to
docker port myapp
Step 3: Apply the Correct Fix
Fix A — Restart a Crashed Upstream Service
If the service is inactive or failed, restart it:
systemctl restart php8.1-fpm
systemctl restart myapp
# Verify it is now running
systemctl is-active php8.1-fpm
To prevent future crashes from causing downtime, configure automatic restarts in the systemd unit:
# /etc/systemd/system/myapp.service
[Service]
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=3
Apply: systemctl daemon-reload && systemctl enable myapp
Fix B — Correct a Socket or Port Mismatch
This is the most common "silent" cause: the backend is running but on a different address than nginx expects. Compare carefully:
# /etc/nginx/sites-available/mysite — what nginx is trying to connect to
upstream php_backend {
server unix:/run/php/php8.1-fpm.sock;
}
# /etc/php/8.1/fpm/pool.d/www.conf — what PHP-FPM is actually listening on
listen = /run/php/php8.1-fpm.sock
These must match exactly — including whether you use a Unix socket vs TCP address. After correcting the mismatch:
nginx -t && systemctl reload nginx
Fix C — Resolve Unix Socket Permission Errors
When the log shows Permission denied on the socket, set socket ownership in the PHP-FPM pool configuration:
# /etc/php/8.1/fpm/pool.d/www.conf
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Verify nginx runs as the same user:
grep 'user' /etc/nginx/nginx.conf
# Should output: user www-data;
Restart both services: systemctl restart php8.1-fpm && systemctl reload nginx
Fix D — Increase Proxy Timeout Values
For backends that are legitimately slow (report generation, large uploads, complex queries), the default timeouts may be too short. Tune them per location block:
server {
location /api/ {
proxy_pass http://mybackend;
proxy_connect_timeout 10s; # time to establish connection
proxy_send_timeout 60s; # time to transmit request to backend
proxy_read_timeout 120s; # time to wait for backend response
proxy_buffering on;
}
# For WebSocket or long-polling endpoints
location /ws/ {
proxy_pass http://mybackend;
proxy_read_timeout 3600s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Apply: nginx -t && systemctl reload nginx
Fix E — Resolve SSL Handshake Failures to HTTPS Upstreams
When proxying to a backend over HTTPS and the log shows SSL_do_handshake() failed:
location / {
proxy_pass https://backend-service;
# For internal services with self-signed certificates
proxy_ssl_verify off;
proxy_ssl_server_name on;
proxy_ssl_protocols TLSv1.2 TLSv1.3;
# For external services — use proper CA verification instead
# proxy_ssl_verify on;
# proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
# proxy_ssl_session_reuse on;
}
Fix F — Handle Upstream Pool Exhaustion (502 Under Load)
Under high traffic, all upstream workers may be busy. Configure automatic failover:
upstream mybackend {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
keepalive 32;
}
server {
location / {
proxy_pass http://mybackend;
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
}
Step 4: Confirm the Fix
# Validate nginx config syntax before reloading
nginx -t
# Reload nginx without dropping existing connections
systemctl reload nginx
# Monitor error log in real time to confirm errors have stopped
tail -f /var/log/nginx/error.log
# Test the endpoint directly
curl -sv https://example.com/api/health 2>&1 | grep '< HTTP'
A 200 OK (or your expected status) confirms the 502 is resolved.
Preventing Future 502 Errors
- Monitor upstream process health with Prometheus + nginx-exporter or a simple systemd watchdog.
- Set
Restart=on-failurein every backend service unit so crashes self-heal within seconds. - Use
proxy_next_upstreamto automatically retry failed requests on available upstream workers. - Alert on error log patterns: ship nginx logs to a log aggregator (Loki, ELK) and alert when upstream error rates spike.
- Set realistic worker_processes and worker_connections in nginx.conf to prevent queue saturation at high traffic.
Frequently Asked Questions
#!/usr/bin/env bash
# nginx-502-diagnose.sh — rapid 502 Bad Gateway diagnostic script
# Run as root or with sudo on the nginx host
set -euo pipefail
echo '=== nginx 502 Bad Gateway Diagnostic ==='
echo
# 1. Check nginx process and config validity
echo '--- [1] nginx status and config ---'
systemctl is-active nginx || echo 'WARNING: nginx is not running'
nginx -t 2>&1
echo
# 2. Show last 30 upstream errors from error log
echo '--- [2] Recent upstream errors ---'
grep 'upstream' /var/log/nginx/error.log 2>/dev/null | tail -30 \
|| echo 'No upstream errors in default log path'
echo
# 3. Show all upstream addresses nginx is configured to use
echo '--- [3] Configured upstream addresses ---'
grep -rE '(proxy_pass|fastcgi_pass|uwsgi_pass|server\s)' \
/etc/nginx/sites-enabled/ /etc/nginx/conf.d/ 2>/dev/null \
| grep -v '#' | sort -u
echo
# 4. Check common backend services
echo '--- [4] Backend service status ---'
for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm php7.4-fpm \
myapp backend api node gunicorn uvicorn puma; do
if systemctl list-units --full --all 2>/dev/null | grep -q "${svc}.service"; then
echo -n " ${svc}: "
systemctl is-active "${svc}" 2>/dev/null || true
fi
done
echo
# 5. Check listening TCP ports (common backend ports)
echo '--- [5] Listening on common backend ports ---'
ss -tlnp | grep -E ':80\b|:443\b|:3000|:4000|:8000|:8080|:8443|:9000|:9090' || true
echo
# 6. Check Unix socket files
echo '--- [6] PHP-FPM socket files ---'
find /run /tmp /var/run -name '*.sock' -type s 2>/dev/null | head -20 || true
echo
# 7. Show nginx worker user vs socket ownership
echo '--- [7] nginx worker user ---'
grep -E '^user' /etc/nginx/nginx.conf | head -3
echo
# 8. Quick HTTP health check of upstream (edit address as needed)
UPSTREAM_HOST=${1:-127.0.0.1}
UPSTREAM_PORT=${2:-8080}
echo "--- [8] TCP connectivity test to ${UPSTREAM_HOST}:${UPSTREAM_PORT} ---"
timeout 3 bash -c "echo >/dev/tcp/${UPSTREAM_HOST}/${UPSTREAM_PORT}" \
&& echo 'Port OPEN' \
|| echo 'Port CLOSED or connection refused'
echo
# 9. Test nginx config and reload if valid
echo '--- [9] Reload nginx if config is valid ---'
if nginx -t 2>/dev/null; then
systemctl reload nginx
echo 'nginx reloaded successfully'
else
echo 'ERROR: nginx config invalid — fix errors above before reloading'
fi
echo
echo '=== Diagnosis complete. Review output above. ==='
echo 'If the upstream port is CLOSED, restart your backend service:'
echo ' systemctl restart <service-name>'
echo 'If socket permissions are wrong, check listen.owner in PHP-FPM pool config.'Error Medic Editorial
The Error Medic Editorial team is composed of senior DevOps engineers, SREs, and Linux systems administrators with collective experience running high-traffic nginx deployments across cloud, bare-metal, and containerized environments. We write troubleshooting guides grounded in real incident postmortems — not guesswork.
Sources
- https://nginx.org/en/docs/http/ngx_http_proxy_module.html
- https://nginx.org/en/docs/http/ngx_http_upstream_module.html
- https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
- https://stackoverflow.com/questions/17497325/nginx-502-bad-gateway-php-fpm
- https://github.com/nicholasgasior/nginx/issues/64
- https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/