Problem

A request returns 502 Bad Gateway, and your nginx error log carries no live upstreams while connecting to upstream. This is not the same as a single backend failing a connection. It means nginx looked at the entire upstream group and found that every server in it was, at that moment, ineligible to receive the request — so there was nothing to proxy to, and it returned 502 without attempting a connection. The first question worth answering is whether nginx ejected your servers (passive health checks knocked them all out) or whether your config left it with nothing to route to in the first place, because those are two completely different bugs.

Symptoms

  • The client sees a plain 502 Bad Gateway; the nginx error log shows no live upstreams while connecting to upstream.
  • The backend responds fine when you curl it directly — the problem is nginx’s routing decision, not the app.
  • With multiple backends, it’s intermittent and correlates with load spikes: it flares for a few seconds, then clears on its own.
  • With a single backend, it’s tied to a config keyword (down, backup, max_conns) or a DNS/resolver failure, and no amount of restarting the app changes it.

What this error actually means

nginx keeps a running verdict on each server in an upstream block. When you list more than one server, it runs passive health checks: it watches real proxied requests, and if a server accumulates max_fails failed attempts within fail_timeout, nginx marks it unavailable and routes around it until fail_timeout expires, then tentatively tries it again. The defaults are aggressive — max_fails=1 and fail_timeout=10s — so a single failed attempt is enough to bench a server for ten seconds.

no live upstreams is what nginx logs when all the servers in the group are benched simultaneously. There’s no eligible peer left, so it can’t even try a connection; it just returns 502. That’s why it so often looks like a mystery: by the time you curl the backend, fail_timeout has elapsed and everything’s “fine” — but during the window, one bad moment took out the whole pool.

The way this cascades under load is the part people miss. A traffic spike makes one backend slow enough to trip max_fails; nginx benches it and pushes its share onto the survivors; the extra load tips the next one over; within a second or two every server has tripped and the whole group is down for fail_timeout, even though none of them actually crashed. It’s a self-inflicted outage built out of overly twitchy defaults.

The single-server case: it isn’t max_fails

Here’s the trap that sends people down the wrong path. If your upstream block has exactly one server, tuning max_fails and fail_timeout does nothing, because nginx ignores them. From the upstream module docs: “If there is only a single server in a group, max_fails, fail_timeout and slow_start parameters are ignored, and such a server will never be considered unavailable.”

So a lone backend is never ejected by passive health checks. If you’re getting no live upstreams with a single server, the cause is elsewhere, and it’s almost always one of these:

  • The server is marked down. A leftover down on the server line (or a config template that added it) means nginx treats it as permanently unavailable. One down server = zero live servers.
  • max_conns is saturated. If you capped the server’s concurrent connections and they’re all in use, nginx has no slot to open, so the server counts as not live for a new request.
  • Everything is backup. If every entry is marked backup and there’s no primary, there is no live primary to serve — the backups only activate when a primary goes down, and there isn’t one.
  • A resolver / variable upstream failed to resolve. If the upstream address is a variable resolved at request time and DNS lookup fails, nginx has no address to connect to and reports no live upstream.

Each of these is a config or DNS problem, not a health-check tuning problem. Reaching for max_fails=3 here fixes nothing.

Diagnose with DechoNet

  • HTTP Check confirms what the outside world actually receives — that the final status is a 502 coming from nginx, and lets you compare a route that fails against one that works. If the 502 is intermittent, repeated checks help you see whether it correlates with load (a cascade) or is constant (a config trap). Pair it with the exact nginx error-log phrase, which is the real diagnostic key.

Resolution Checklist

  • Read the exact log phrase first. no live upstreams (whole group unavailable) is a different bug from connect() failed or upstream timed out (one attempt failed). Don’t fix the wrong one.
  • Count the servers in the upstream block. One server → it’s not max_fails; check for down, backup, max_conns, or a resolver failure. Multiple servers → suspect passive-health-check ejection.
  • For the multi-server cascade, loosen the twitchy defaults: raise max_fails (e.g. 3) and/or fail_timeout so one slow moment doesn’t bench a healthy server, and fix the underlying reason a backend was slow (saturation, timeouts, GC pauses).
  • Grep the config for a stray down and for a block where every entry is backup with no primary. Remove the leftover down; give the group a real primary.
  • If the upstream uses a variable + resolver, verify the resolver is reachable and the name resolves — a DNS failure here reads as “no live upstreams.”
  • Re-run HTTP Check and confirm the route no longer returns 502.

When to Escalate

  • If the 502s track traffic spikes and clear within fail_timeout, the real problem is upstream capacity or latency, not nginx — investigate saturation, slow queries, connection-pool limits, and timeouts on the backend rather than only widening max_fails.
  • If you run nginx behind a managed load balancer or ingress controller, remember there may be a second layer of health checks with its own thresholds; a backend can be “up” to one layer and benched by the other.
  • For the closely related case where a single backend returns a 502 because its response headers overflow nginx’s buffer, see upstream sent too big header (nginx 502); for the general 502 decision tree, see the 502 Bad Gateway checklist.

Check your own domain now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.