RCWRCW IT TrainingFree hands-on labs & simulators← Back to home
NGINX · Troubleshooting guide

NGINX 502 and 504: Reading Upstream Errors Like the Error Log Does

A 502 means NGINX gave up talking to the backend; a 504 means the backend took too long answering. The error log line tells you exactly which. This guide walks the five upstream errors you will actually meet.

Published August 29, 2026 · RCW IT Training

The one log line that matters

Everything starts in the error log, because the access log only says 502. The upstream error string is the diagnosis:

tail -f /var/log/nginx/error.log
# example:
2026-08-29 10:14:03 [error] 1234#0: *5678 connect() failed (111: Connection refused)
  while connecting to upstream, client: 203.0.113.9, server: app.example.com,
  request: "GET /api/orders HTTP/1.1", upstream: "http://127.0.0.1:8000/api/orders"

The five upstream errors and what each means

(111: Connection refused) — nothing is listening.

The backend process is down, crashed, or listening on a different port/interface than the proxy_pass target. Check systemctl status for the app, and ss -tlnp | grep :8000 for the listener. After a deploy, this is the classic "the service restarted on a new port" or "the app bound to 127.0.0.1 but NGINX reaches the container IP" mismatch.

(104: Connection reset by peer) — the backend killed the connection.

The app accepted then closed early: it crashed mid-request, its accept queue overflowed, or a keepalive connection NGINX was reusing was already closed server-side. If it correlates with traffic spikes, raise the app's backlog (e.g. somaxconn, the app server's listen backlog) and enable upstream keepalive with the right order of directives:

upstream app {
    server 127.0.0.1:8000;
    keepalive 32;          # must come AFTER the server lines
}
server {
    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://app;
    }
}
(110: Connection timed out) — the network, not the app.

SYN sent, no answer: a firewall drop, a security group, or the upstream IP unreachable from this worker. curl -v telnet://upstream:port from the NGINX host isolates it in seconds.

upstream timed out (110) while reading upstream — the 504.

The connection succeeded but the backend took longer than proxy_read_timeout (default 60s) to answer. The backend is slow, not dead: a long query, a synchronous report, a locked table. Either make the endpoint faster or raise the timeout — but raising it without fixing the slow endpoint just moves the failure to the browser.

(2: No such file or directory) while connecting to upstream — the Unix socket is gone.

proxy_pass http://unix:/run/app.sock: targets a socket the app no longer creates, usually after the app switched to a TCP port or the socket path changed in a deploy. Compare the path in proxy_pass with ls -l /run/ and the app's config.

502 only on big uploads or downloads: buffer problems

A 502 with upstream sent too big header or upstream prematurely closed connection while reading upstream on large responses is a buffering problem: the response header or body exceeds NGINX's buffers and the temp directory cannot absorb it.

proxy_buffer_size        16k;   # header buffer
proxy_buffers            8 32k;
proxy_busy_buffers_size  64k;
# and make sure the temp path is writable and not full:
df -h /var/lib/nginx

A full disk or a /var/lib/nginx owned by the wrong user produces 502s that look random but follow request size.

Config changes: test before reload

Most "NGINX broke after a change" incidents are untested reloads. The discipline is two commands:

nginx -t                 # syntax + file existence
nginx -s reload          # graceful; existing connections finish
# if a reload made things worse:
nginx -s reload          # again, after fixing — reload is not restart
systemctl status nginx   # worker count, recent failures

nginx -T dumps the full effective configuration with includes resolved — the fastest way to find which file really sets the timeout you think you changed.

Timeouts that match reality

DirectiveDefaultGuidance
proxy_connect_timeout60sDrop to 5s — a refused/upstream should fail fast, not hang
proxy_read_timeout60sMatch your slowest legitimate endpoint + margin; alert when p99 approaches it
proxy_send_timeout60sRarely the culprit; matters for big uploads to slow backends

Two incident patterns you will recognise

The post-deploy 502 storm.

Every deploy restarts the app; for a second nothing listens on the port and NGINX logs a burst of (111: Connection refused). A few 502s per deploy is normal; hundreds means the app boots slowly and the process manager marks it ready too early. Fix at the app level (readiness = actually serving), and add proxy_next_upstream error http_502; with a second backend so a refused connection retries the healthy node instead of surfacing.

The 504 that is really a database incident.

When 504s appear across unrelated endpoints at once, the backend is not slow — it is blocked, usually on a database lock. The NGINX log shows upstream timed out, the app log shows nothing (requests never finish), and the database shows an idle-in-transaction blocker. Chasing NGINX timeouts wastes the incident; the queue of waiting upstream connections is the clue to look downstream.

gRPC and HTTP/2 backends.

proxy_pass speaks HTTP/1.1 to the upstream by default. A gRPC service behind NGINX needs grpc_pass with an http2 upstream, or every call fails in ways that look like random 502s. If the backend is gRPC, the location block must use the grpc directives, not the proxy ones.

Prevention checklist

  • Add upstream health checks (active via health_check on NGINX Plus, or passive max_fails/fail_timeout) so one dead backend stops taking traffic.
  • Log $upstream_response_time and $upstream_status in the access log — you then see backend latency and backend status without opening the error log.
  • Alert on 5xx rate per upstream, not per site; a single misbehaving backend hides in a site-wide number.
  • Keep proxy_connect_timeout low and retries (proxy_next_upstream) limited to idempotent methods.
Key takeaway: The errno in the NGINX error log is the diagnosis: 111 refused means the backend is down, 104 reset means it died mid-request, 110 connecting means network, 110 reading means a slow backend, and "no such file" means a vanished socket. Fix the backend first; timeouts are a tuning layer, not a cure.