Illustrative Nginx lesson with an engineer using a laptop beside a client–Nginx–application diagram.

WIUNIX ACADEMY

Nginx 502 and 504: identify the failing upstream phase

Separate connection refusal, upstream queueing, slow response headers and proxy timeouts using bounded probes and correlated timing.

Advanced30 min readUpdated
Make this guide yours

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Debian 13Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment5 editable values

THE OUTCOME

What will you have at the end?

Separate connection refusal, upstream queueing, slow response headers and proxy timeouts using bounded probes and correlated timing.

Supported operating systems and versions

Primary-documentation review; no production execution or universal installation test is claimed. Examples assume existing tools on Linux and must be adapted to authorized deployment context.

Before you begin

  • Authorized operator access, existing tools named in the panels, and a private place to retain diagnostic evidence.
  • Use a staging environment or an approved read-only production investigation; review disruptive changes separately.

A status code is a symptom, not a root cause

A 502 usually means the gateway could not obtain a usable upstream response; a 504 means its wait expired. Neither tells you whether the fault is a refused TCP connection, an overloaded worker pool, an invalid protocol response or a deadline imposed by another proxy. Work on one known read-only endpoint and one UTC time window. This guide assumes host Nginx proxies plain HTTP to one known address. FastCGI, gRPC, Unix sockets and a container-only network need their own transport checks.

01

LET’S BUILD

Check the selected distribution and required tools first

Run on the authorized diagnostic host or disposable lab VM. Compare the installed OS and required packages below with the selected path. Obtain missing tools from the distribution’s enabled official repositories through your approved package-management process; this workflow does not reinstall an existing server or switch its repository.

Debian 13 and 12 use the package names below. Debian 12 is in LTS; architecture and individual package support must be checked rather than inferred from the major version.

An existing Nginx installation is required. Packaged deployments normally use nginx.service and nginx.conf under /etc/nginx, but discover active includes and logs from the effective configuration. Do not assume Ubuntu sites-enabled layout on an RPM host.

Effective configuration can contain private deployment data; keep it private. Set this guide’s error-log field to the actual configured destination.

bash

Authorized terminal on the host identified in this section

cat /etc/os-release
dpkg-query -W curl iproute2
command -v curl ss nginx
sudo nginx -t
sudo nginx -T 2>&1 | sed -n '1,100p'
systemctl show nginx.service -p FragmentPath -p DropInPaths -p ExecStart
How do I know it worked?

Required packages and tools are identified; resolve missing tools before continuing.

02

LET’S BUILD

Record versions and the exact endpoint

On the proxy host, record time and Nginx build, then request the authorized public hostname without credentials or query parameters. The response header timing includes DNS, TCP and TLS effects on this path; it is not an application-only measurement. Use an existing harmless root endpoint, or adapt the URL to an approved health route before running.

bash

Authorized Linux terminal

date -u +'%Y-%m-%dT%H:%M:%SZ'
nginx -v
curl --noproxy '*' --connect-timeout 3 --max-time 15 -sS -o /dev/null -w 'status=%{http_code} connect=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' 'https://app.example.com/'
How do I know it worked?

Record the HTTP status or curl transport error with the UTC timestamp. A timeout at 15 seconds is the probe deadline, not proof of Nginx’s own timeout.

03

LET’S BUILD

Check host policy independently

Inspect kernel messages in the same failure window. On a host with active AppArmor profiles, a denial can be independent of Unix permissions or network reachability. Do not disable the profile just to pass the check; correlate it with application logs and the actual host policy.

bash

Authorized terminal on the host identified in this section

sudo journalctl -k --since '-10 min' --no-pager -n 120
How do I know it worked?

Any suspected policy failure is tied to specific evidence; enforcement and firewall configuration remain unchanged.

04

LET’S BUILD

Bypass the proxy without changing exposure

From the Nginx host, call the existing private backend with the same Host value. This is an authorized read-only probe, not permission to publish a private port. The HTTP Host may select a virtual application; HTTPS upstreams require verified TLS/SNI instead of this plain-HTTP example.

bash

Authorized Linux terminal

curl --noproxy '*' --connect-timeout 3 --max-time 15 -H 'Host: app.example.com' -sS -o /dev/null -w 'status=%{http_code} connect=%{time_connect} first_byte=%{time_starttransfer} total=%{time_total}\n' 'http://127.0.0.1:8000/'
How do I know it worked?

A repeatable private response establishes a comparison. Immediate refusal differs from a connection timeout and from a slow first byte.

05

LET’S BUILD

Separate a missing listener from a saturated application

On the upstream host, inspect listeners and the application unit. The port must match the actual proxy configuration; a process listening only in another network namespace is not a host listener. A unit marked active can still serve errors or queue requests. Avoid repeatedly restarting the process before collecting its recent failure evidence.

bash

Authorized Linux terminal

sudo ss -lntp 'sport = :8000'
sudo systemctl status 'example-app.service' --no-pager
sudo journalctl -u 'example-app.service' --since '-10 min' --no-pager -n 100
How do I know it worked?

Match the exact PID/address/port and recent application errors. Logs may contain private values; review locally and redact before sharing.

Explore all tutorials ↗