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:

bash
sudo nginx -t

Failure example:

text
nginx: [emerg] unexpected ";" in /etc/nginx/sites-enabled/api:14
nginx: configuration file /etc/nginx/nginx.conf test failed

Open the file at the line number, fix syntax, re-test.

Success:

bash
sudo systemctl reload nginx

If reload fails, check systemctl status nginx and error.log.

HTTP Status Quick Reference

CodeNginx layerCommon causes
404Nginx or upstreamWrong root, try_files, missing file, bad proxy_pass path
403NginxPermissions, deny, directory index off
413Nginxclient_max_body_size too small
499Client closedHarmless spikes; slow clients
502Upstream unreachableApp down, wrong port, refuse connection
504Upstream timeoutSlow query; low proxy_read_timeout

502 Bad Gateway

Symptoms: browser/API client sees 502; error log may show:

text
connect() failed (111: Connection refused) while connecting to upstream

Checklist:

bash
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.log

Fixes:

  • Start Spring Boot / Node / systemd unit
  • Correct port in proxy_pass
  • Bind app to 127.0.0.1 on expected port

504 Gateway Timeout

Increase proxy timeouts:

nginx
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

bash
ls -la /var/www/myapp/index.html
grep -E 'root|try_files' /etc/nginx/sites-enabled/myapp

Fix root path, deploy files, or SPA try_files ... /index.html.

API through proxy

Compare direct vs proxied:

bash
curl -i http://127.0.0.1:8080/users
curl -i -H "Host: api.example.com" http://127.0.0.1/users

Often trailing slash / prefix strip mismatch—see Proxy Headers and Path Rules.

403 Forbidden

bash
namei -l /var/www/myapp/index.html

Ensure Nginx user (www-data) can traverse directories (x) and read files (r):

bash
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

nginx
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

bash
sudo nginx -t

If 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:

bash
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

bash
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

bash
sudo ss -tlnp | grep nginx
sudo ufw status

Cloud security groups must allow 80/443 from the internet.

Config Not Applied

  • Edited sites-available but forgot symlink to sites-enabled
  • Edited file not included by nginx.conf
  • Reload skipped after successful nginx -t
  • Typo in duplicate server block—last reload wins per worker gradually

Escalation Checklist

  1. nginx -t
  2. systemctl status nginx
  3. tail error.log + tail access.log
  4. curl upstream directly
  5. curl through Nginx with correct Host
  6. ss -tlnp for ports
  7. 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.