Nginx Troubleshooting Guide
Introduction
When a site returns 502, 404, or refuses to reload, a structured checklist beats random config edits. This chapter maps common symptoms to causes, tools (nginx -t, curl, ss), and fixes—the companion to access/error logs from the previous chapter.
Prerequisites
Step Zero: nginx -t
Always start here after any config change:
sudo nginx -tFailure example:
nginx: [emerg] unexpected ";" in /etc/nginx/sites-enabled/api:14
nginx: configuration file /etc/nginx/nginx.conf test failedOpen the file at the line number, fix syntax, re-test.
Success:
sudo systemctl reload nginxIf reload fails, check systemctl status nginx and error.log.
HTTP Status Quick Reference
| Code | Nginx layer | Common causes |
|---|---|---|
| 404 | Nginx or upstream | Wrong root, try_files, missing file, bad proxy_pass path |
| 403 | Nginx | Permissions, deny, directory index off |
| 413 | Nginx | client_max_body_size too small |
| 499 | Client closed | Harmless spikes; slow clients |
| 502 | Upstream unreachable | App down, wrong port, refuse connection |
| 504 | Upstream timeout | Slow query; low proxy_read_timeout |
502 Bad Gateway
Symptoms: browser/API client sees 502; error log may show:
connect() failed (111: Connection refused) while connecting to upstreamChecklist:
sudo ss -tlnp | grep -E ':8080|:3000'
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
sudo tail -n 30 /var/log/nginx/error.logFixes:
- Start Spring Boot / Node / systemd unit
- Correct port in
proxy_pass - Bind app to
127.0.0.1on expected port
504 Gateway Timeout
Increase proxy timeouts:
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;Also optimize slow backend queries—Nginx cannot fix a stuck database.
404 Not Found
Static site
ls -la /var/www/myapp/index.html
grep -E 'root|try_files' /etc/nginx/sites-enabled/myappFix root path, deploy files, or SPA try_files ... /index.html.
API through proxy
Compare direct vs proxied:
curl -i http://127.0.0.1:8080/users
curl -i -H "Host: api.example.com" http://127.0.0.1/usersOften trailing slash / prefix strip mismatch—see Proxy Headers and Path Rules.
403 Forbidden
namei -l /var/www/myapp/index.htmlEnsure Nginx user (www-data) can traverse directories (x) and read files (r):
sudo chown -R www-data:www-data /var/www/myapp
sudo find /var/www/myapp -type d -exec chmod 755 {} \;
sudo find /var/www/myapp -type f -exec chmod 644 {} \;On RHEL with SELinux, check AVC denials in /var/log/audit/audit.log.
413 Request Entity Too Large
client_max_body_size 50M;Place in http, server, or location for uploads. Align Spring Boot spring.servlet.multipart.max-file-size with Nginx.
Permission Denied on Reload
sudo nginx -tIf config references unreadable cert or log path, fix ownership/permissions.
Binding to port 80 requires root or CAP_NET_BIND_SERVICE—use sudo systemctl start nginx.
Wrong Site Served
Multiple server blocks on port 80:
curl -I -H "Host: intended.example.com" http://127.0.0.1/Check server_name, default_server, and symlink in sites-enabled.
curl Debugging Recipes
curl -I http://127.0.0.1/
curl -v -H "Host: api.example.com" http://127.0.0.1/api/health 2>&1 | head -40
curl -k -I https://example.com/Code explanation:
-I— headers only-v— verbose handshake-k— skip cert verify (self-signed dev only)
Port and Firewall
sudo ss -tlnp | grep nginx
sudo ufw statusCloud security groups must allow 80/443 from the internet.
Config Not Applied
- Edited
sites-availablebut forgot symlink tosites-enabled - Edited file not included by
nginx.conf - Reload skipped after successful
nginx -t - Typo in duplicate
serverblock—last reload wins per worker gradually
Escalation Checklist
nginx -tsystemctl status nginxtail error.log+tail access.logcurlupstream directlycurlthrough Nginx with correctHostss -tlnpfor ports- Compare config to working example in this track
FAQ
Nginx running but site down?
DNS, firewall, or wrong server block—not always Nginx process crash.
Intermittent 502?
App restarting, OOM kill, or upstream pool with dead server—check journalctl -u myapp.
Works on HTTP not HTTPS?
Separate server blocks—fix 443 ssl_certificate paths and Certbot renewal.
How to test config without reload?
nginx -t only validates syntax—not full runtime behavior.
strace nginx?
Last resort on staging—shows syscalls; heavy overhead.
Still stuck?
Minimal repro: one server, one location, one proxy_pass—add complexity back slowly.