E-NO Logo
EN FR
Nginx troubleshooting 9 Min Read

Nginx troubleshooting with practical examples: practical implementation guide

calendar_today Published: 2026-07-09
update Last Updated: 2026-07-09
analytics SEO Efficiency: 97%
Technical guide illustration for Nginx troubleshooting with practical examples: practical implementation guide.

Nginx is a fast, flexible reverse proxy and web server, but production issues can be tricky when symptoms overlap. This guide gives you a practical troubleshooting workflow with safe commands, log tips, and mini runbooks for common failures. You will be able to:

  • Identify what broke and where: Nginx, the network, or the upstream app.
  • Read and filter access/error logs to see cause and effect.
  • Validate config safely before reloading.
  • Fix common errors like 502, 504, 413, 403, 404, and TLS handshake failures.
  • Apply the same approach on Linux hosts, in Docker, and with Kubernetes Ingress.

Hands-on examples are included so you can practice locally and reduce downtime when it matters.

Workflow Overview

Use this loop in production and keep your steps small. Each step includes practical commands and tips.

  1. Confirm the symptom
  • Capture exact error and URL path. Example: 502 on GET /api.
  • Reproduce with curl to avoid client-side noise:
curl -iv http://example.com/api
curl -kiv https://example.com/secure
  1. Check process and ports
# Is Nginx running?
systemctl status nginx || service nginx status

# Is it listening where you expect?
ss -tulpn | grep -E ':80|:443'

# What is using the port if not Nginx?
fuser -v 80/tcp 443/tcp || lsof -i :80 -i :443
  1. Inspect logs fast
  • Default locations:
  • /var/log/nginx/error.log
  • /var/log/nginx/access.log
# Tail and filter fresh errors
tail -Fn200 /var/log/nginx/error.log | grep -iE 'error|crit|alert'

tail -Fn200 /var/log/nginx/access.log | awk '{print $1, $4, $6, $7, $9, $10}'

Tip: temporarily increase error log detail in a test environment:

error_log /var/log/nginx/error.log info;  # or debug if compiled with debug
  1. Validate configuration safely
# Always test before reload
grep -RIn 'error|warn' /etc/nginx/*.conf /etc/nginx/conf.d || true
nginx -t  # syntax and basic semantics check

# Back up before changes
cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%F-%H%M)
  1. Probe the upstream and the network
  • If you use proxy_pass http://app:3000, test the target directly from the Nginx host:
curl -iv http://127.0.0.1:3000/health || curl -iv http://app:3000/health

# In Docker: ensure the name resolves and port is reachable
# From inside the Nginx container:
docker exec -it nginx sh -lc 'getent hosts app && curl -sS http://app:3000/health'
  1. TLS sanity checks
# Show certificate chain and SNI handling
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
  1. Apply targeted fixes using mini runbooks (below)
  1. Reload safely and atomically
# Test again, then reload without dropping connections
nginx -t && systemctl reload nginx || service nginx reload
  1. Verify and watch for regressions
curl -Is http://example.com/ | head -n1
curl -Is https://example.com/ | head -n1

# Quick smoke against key paths
for p in / /health /api/version; do curl -sSfo /dev/null -w '%{http_code} %{url_effective}\n' http://example.com"$p"; done
  1. Harden to prevent recurrence
  • Add health endpoints on upstreams.
  • Tune timeouts and buffers based on observed traffic.
  • Ship logs centrally; add alerts for spike in 5xx and 499.

Mini runbooks: common Nginx errors and fixes

Each sub-section gives symptoms, root-cause checks, and safe fixes.

502 Bad Gateway (proxy to app)

Symptoms: 502 on proxied routes. Error log may show upstream connect or prematurely closed connection.

Checks:

# Is upstream up?
curl -iv http://127.0.0.1:3000/health

# DNS resolution if using a name in proxy_pass
getent hosts app || dig +short app

# Socket or PHP-FPM example
ls -l /run/php/php-fpm.sock && ss -xl | grep php-fpm

Config pitfalls and fixes:

# Ensure correct upstream and headers
location /api/ {
    proxy_pass http://127.0.0.1:3000/;  # note trailing slash behavior
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_connect_timeout 5s;
    proxy_read_timeout 60s;
}
  • If Docker DNS is used, add a resolver in http{} when proxying to names that change:
resolver 127.0.0.11 valid=30s;  # Docker embedded DNS
  • For PHP-FPM:
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php-fpm.sock;  # or 127.0.0.1:9000
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

504 Gateway Timeout

Symptoms: long waits ending in 504.

Checks and fixes:

# Increase timeouts to match upstream behavior
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

# Keep upstream alive to reduce handshake overhead
upstream app {
    server 127.0.0.1:3000;
    keepalive 16;
}

Also review upstream logs for slow queries or GC pauses.

413 Request Entity Too Large

Symptoms: uploads fail; error log shows client intended to send too large body.

Fix:

# Place in http, server, or location as needed
client_max_body_size 50m;

Reload and retest the upload path only.

404 Not Found and 403 Forbidden

404 common causes:

  • Wrong root vs alias mapping.
  • try_files mis-ordered.
  • Location precedence surprises.

403 common causes:

  • Missing index file.
  • File perms or SELinux context.

Fix patterns:

# Static files from /var/www/site/public
server {
    root /var/www/site/public;
    index index.html index.htm;

    location /images/ {
        alias /mnt/media/img/;  # alias replaces root for this block
        autoindex off;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Linux permissions and SELinux:

# Ensure nginx user can read directories
namei -l /var/www/site/public/index.html

# SELinux context repair (on SELinux-enabled distros)
sudo restorecon -R /var/www/site || sudo chcon -R -t httpd_sys_content_t /var/www/site

TLS handshake failures

Symptoms: curl -k works but browsers fail; error log shows SSL errors or no shared cipher.

Fix patterns:

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    add_header Strict-Transport-Security 'max-age=31536000' always;
}

Verify chain and SNI:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null | openssl x509 -noout -subject -issuer -dates

If you see the wrong certificate, check server_name matching and that only one default_server handles 443.

400/431 header issues and 499 client closed request

  • Large headers: increase buffers.
large_client_header_buffers 4 16k;
underscores_in_headers on;  # if your clients send underscores
  • 499 indicates clients closed the connection early. Check upstream latency and client timeouts; reduce response time, keep bodies small.

WebSocket and HTTP upgrade problems

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

location /ws/ {
    proxy_pass http://127.0.0.1:8080/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

Reload or startup failures

Symptoms: nginx -t fails or reload does nothing.

Checks:

nginx -t
journalctl -u nginx -n 100 --no-pager

# Common errors: duplicate listen, unknown directive, bad include

Port conflicts:

ss -tulpn | grep ':80\|:443' || true
# Stop the other process or change Nginx listen ports

Docker, Kubernetes Ingress, and Linux specifics

  • Docker
  • Mount config at /etc/nginx and test inside the container:
docker exec -it nginx sh -lc 'nginx -t && nginx -V && ss -tulpn'
  • Ensure names in proxy_pass resolve inside the container. Consider adding:
resolver 127.0.0.11 valid=30s;
  • Kubernetes Ingress (NGINX controller)
  • Inspect controller logs and events:
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=200
kubectl describe ingress your-ingress -n your-namespace
  • Common knobs: timeouts (annotations), body size, proxy headers. Ensure your Service points to healthy Pods and readinessProbes succeed.
  • Linux basics
  • Firewalls: allow 80 and 443 (ufw, firewalld, iptables).
  • SELinux: verify contexts for content and certificates.
  • Systemd: prefer reload over restart for config changes.

Local Pilot Plan

Start with a small pilot you can inspect locally. Keep scope tight and measurable.

Goals

  • Serve static content at / and proxy /api to a demo app on 127.0.0.1:9000.
  • Exercise 502, 413, and TLS cases on demand.
  • Practice safe reloads with zero failed requests during change.

Setup

  1. Demo upstream
# Simple demo app (Python example)
python3 -m http.server 9000 --bind 127.0.0.1 &
  1. Nginx config
# /etc/nginx/conf.d/pilot.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    location /api/ {
        proxy_pass http://127.0.0.1:9000/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        client_max_body_size 5m;  # adjust in tests
    }
}
  1. Validate and reload
nginx -t && systemctl reload nginx || service nginx reload
  1. Test cases
  • Happy path:
curl -sS -w '%{http_code}\n' -o /dev/null http://localhost/
curl -sS -w '%{http_code}\n' -o /dev/null http://localhost/api/
  • 502 drill: stop the upstream and confirm Nginx behavior, then restore.
pkill -f 'http.server 9000' || true
curl -sS -v http://localhost/api/
python3 -m http.server 9000 --bind 127.0.0.1 &
  • 413 drill: upload a file larger than client_max_body_size and watch error.log.
head -c 10M </dev/urandom > big.bin
curl -sS -F [email protected] http://localhost/api/upload
  • TLS drill (optional): add a self-signed cert and a 443 server, then verify with openssl.
  1. Success criteria
  • 200 responses for / and /api with upstream up.
  • 502 appears when upstream is down; clears when back up.
  • 413 occurs for oversized body; success after raising client_max_body_size.
  • No errors in nginx -t and clean reloads.
  • error.log clearly shows each test case.
  1. Rollback
cp -a /etc/nginx/nginx.conf.bak.* /etc/nginx/nginx.conf 2>/dev/null || true
# Or move pilot.conf out and reload
mv /etc/nginx/conf.d/pilot.conf /etc/nginx/conf.d/pilot.conf.off
nginx -t && systemctl reload nginx || service nginx reload

Conclusion

Use the loop: confirm the symptom, check process and ports, read the logs, validate config, test the upstream and TLS, apply a focused fix, reload safely, and verify. Practice with the local pilot until each mini runbook feels routine. Then apply the same patterns to Docker and Kubernetes Ingress: verify name resolution, health endpoints, timeouts, and headers. Keep changes small, rely on logs and curl for fast feedback, and document the exact commands that worked so your team can repeat them with confidence.

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL