Skip to content

How to Debug nginx Errors

Key idea:

nginx errors — most often config syntax (nginx -t catches), permissions (error.log), upstream issues (502/504). Debug steps: tail error.log, curl test from different angles, strace on process. Top 5 errors: 502 Bad Gateway (upstream down), 504 Gateway Timeout (slow upstream), 403 Forbidden (permissions), 400 Bad Request (header size), worker process crashes.

Below: step-by-step, working examples, common pitfalls, FAQ.

Step-by-Step Setup

  1. Test config: nginx -t — catches syntax errors before reload
  2. Tail error.log: tail -f /var/log/nginx/error.log
  3. Check access log for request patterns: tail -f /var/log/nginx/access.log
  4. Increase log level: error_log /var/log/nginx/error.log debug; in nginx.conf
  5. Test upstream with curl directly: curl -I http://127.0.0.1:3000/ (skip nginx)
  6. File permissions: ls -la /var/www/site/ — nginx user must be able to read
  7. SELinux/AppArmor (RHEL/Ubuntu): audit2allow -a, aa-status
  8. Reload: systemctl reload nginx (SIGHUP, no downtime)

Working Examples

ScenarioConfig
Config testnginx -t # Ok: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # Errors: nginx: [emerg] unknown directive "incude" in /etc/nginx/nginx.conf:5
502 Bad Gateway debug# error.log: # 2026/04/18 upstream connect() failed (111: Connection refused) while connecting to upstream, client: 1.2.3.4, server: example.com, upstream: "http://127.0.0.1:3000/" # Fix: app on 127.0.0.1:3000 is down. Check: systemctl status myapp ss -tlnp | grep 3000
504 Gateway Timeout# Increase timeouts location /api/ { proxy_pass http://backend; proxy_read_timeout 60s; # default 60s proxy_connect_timeout 10s; proxy_send_timeout 60s; }
413 Entity Too Large# nginx.conf http { client_max_body_size 100m; } # For file uploads — increase. Default 1M is too small
Permission debugsudo -u www-data cat /var/www/site/index.html # check if user has access ls -la /var/www/site/ # owner must read chown -R www-data:www-data /var/www/site chmod -R 755 /var/www/site find /var/www/site -type f -exec chmod 644 {} \;

Common Pitfalls

  • Reload without test — broken config crashes nginx. ALWAYS nginx -t first
  • error.log not rotated — 10 GB file fills disk. Setup logrotate
  • Location blocks order matters: exact match > prefix > regex. Check priority
  • proxy_pass with and without trailing slash behaves differently. proxy_pass http://backend; (no /) vs proxy_pass http://backend/; (with /)
  • SELinux blocks access if file not labeled correctly. chcon -R -t httpd_sys_content_t /var/www

Learn more

Frequently Asked Questions

How to enable debug logging only for one IP?

<code>events { debug_connection 1.2.3.4; }</code> in nginx.conf — debug only for this client.

Real IP through proxy?

Configure <code>set_real_ip_from 10.0.0.0/8;</code> + <code>real_ip_header X-Forwarded-For;</code> — nginx will show client IP instead of proxy.

How to check backend health from nginx?

<code>nginx_upstream_check_module</code> (patch for OSS nginx). Or plain passive health checks via <code>max_fails=3 fail_timeout=30s</code>.

Best tool for debug traffic?

<code>tcpdump</code> on server → capture traffic, analyze in Wireshark. Or DevTools Network tab for client-side.