Error Medic

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.

Last updated:
Last verified:
2,388 words
Key Takeaways
  • 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
Fix Approaches Compared
MethodWhen to UseTimeRisk
Restart upstream service (systemctl restart)Backend process has crashed or stopped responding entirely< 1 minLow — brief downstream interruption
Correct socket or port in nginx upstream blockError log shows 'No such file or directory' or wrong port in upstream address5 minLow — requires nginx reload
Fix Unix socket ownership/permissionsError log shows 'connect() failed (13: Permission denied)'5 minLow — requires PHP-FPM and nginx reload
Increase proxy_read_timeout / proxy_connect_timeoutBackend is running but processing is slow; 502 appears under load or for heavy queries5 minLow — may mask slow backend bugs
Configure proxy_ssl_verify and TLS settingsProxying to an HTTPS upstream; error log shows SSL_do_handshake() failed15 minMedium — disabling verification reduces security on untrusted networks
Add proxy_next_upstream retry logicMultiple upstream workers available; need automatic failover on 50210 minLow — 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-failure in every backend service unit so crashes self-heal within seconds.
  • Use proxy_next_upstream to 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

bash
#!/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.'
E

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

Related Guides