Lab: Nginx 502/504 Troubleshooting¶
Pairs with: Nginx Reverse Proxy and 502/504 Troubleshooting
Environment: A single disposable VM with nginx installed and Python 3 available (for a trivial backend). If you prefer containers over a VM, the files below reproduce the same lab.
Docker Compose version of this lab (optional)
# docker-compose.yml
services:
nginx:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend
backend:
image: python:3.12-alpine
command: python3 -m http.server 8000
# nginx.conf (next to docker-compose.yml)
upstream app_backend {
server backend:8000;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 5s;
}
}
To reproduce the 502 scenario run docker compose stop backend; restore with docker compose start backend.
Starting conditions¶
- nginx installed and running.
- The reverse proxy config from the parent article, pointing at
127.0.0.1:8000. - A trivial backend running and confirmed reachable through nginx: This should return
HTTP/1.1 200 OK.
Instructions¶
Part 1 — Produce a 502¶
- Stop the backend:
kill %1(or find its PID withjobs -l/pgrep -f http.serverand kill it). curl -i http://localhost/and observe the response.- Check
/var/log/nginx/error.log's newest entry.
Part 2 — Produce a 504¶
- Restart the backend, but this time make it artificially slow (simulate a hung request):
- Set a short
proxy_read_timeout 5s;in the nginx config (temporarily, for this test) and reload nginx. curl -i http://localhost/and time how long it takes to receive a response.
Expected results¶
- Part 1:
curlreceivesHTTP/1.1 502 Bad Gatewayessentially instantly (no waiting — nginx knows immediately that the connection was refused). The error log showsconnect() failed (111: Connection refused)referencing127.0.0.1:8000. - Part 2:
curlwaits approximately 5 seconds (matchingproxy_read_timeout) and then receivesHTTP/1.1 504 Gateway Timeout— notably not instant, and not the full 90 seconds the backend would eventually take to respond.
Diagnosis checklist¶
- [ ] Did you confirm the 502 was instant (connection-level failure) vs. the 504 being delayed by roughly the configured timeout (a response that just never arrived in time)?
- [ ] Did you check
error.logfor Part 1, and correctly interpretConnection refusedas "nothing is listening," distinct from a reset or a timeout? - [ ] For Part 2: did you confirm, with a direct
curlto127.0.0.1:8000bypassing nginx entirely, that the backend itself really is the slow part (as opposed to something being wrong specifically in the nginx-to-backend hop)?
Success criteria¶
You can explain, for any 502 or 504 your own production nginx throws in the future, which of these two categories it falls into and what your very next diagnostic command should be — without needing to re-read the parent article.
Cleanup / rollback¶
# Stop any lingering test backend:
pkill -f http.server
# Revert the temporary proxy_read_timeout if you changed it:
sudo sed -i 's/proxy_read_timeout 5s;/proxy_read_timeout 60s;/' /etc/nginx/sites-available/app.conf
sudo nginx -t && sudo systemctl reload nginx
Confirm curl -i http://localhost/ returns a normal 200 OK again (with a real backend running) before considering the environment restored.