nginx 502 Bad Gateway: Finding the Real Culprit

502 means nginx reached for your upstream and got nothing usable back. The error log names the exact reason — it is rarely vague.

What you'll see

Root causes

App not listening where nginx thinks

Upstream socket/port mismatch, or the app crashed. Log shows 'connect() failed (111: Connection refused)'.

SELinux blocking the proxy (RHEL-family)

httpd is not allowed to make network connections by default. Log shows 'Permission denied' on connect.

Slow upstreams timing out

Long requests exceed proxy_read_timeout and get killed, surfacing as 502/504.

Fix it

  1. Read the actual upstream error
    tail -50 /var/log/nginx/error.log
  2. Confirm the app answers on the exact address nginx dials
    curl -v http://127.0.0.1:8080/healthz  # match the upstream line
  3. On RHEL/CentOS/Alma, allow nginx to connect out
    setsebool -P httpd_can_network_connect 1
  4. For slow endpoints, raise timeouts instead of retrying blindly
    proxy_read_timeout 120s;
    proxy_connect_timeout 10s;

Field note

Connection refused = wrong place or dead app. Permission denied = SELinux. Timeout = slow app. The error log always tells you which of the three you have.

Common questions

Why do I get 502 only under load?

The upstream accepts light traffic but exhausts under load: worker/thread pool limits, connection queue, or the app crashing on concurrency. The nginx error log distinguishes 'connect() failed' (upstream refused) from 'upstream prematurely closed' (app died mid-request).

Is a 502 ever nginx's own fault?

Rarely — misconfigured proxy_pass targets (wrong port, wrong protocol header) can produce it, but the cause is still a failed upstream conversation. Fix the upstream path first; nginx is almost always the messenger.

Ship it right the first time

Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.

Browse the template store →

One-time. Yours to modify. Instant download from the NinjaOps template store.