Troubleshooting
Scope: the failures nginx actually presents, each seen in this guide's lab, each read the same way: the status is the symptom, the error log is the diagnosis.
Read every problem in this order
- The status code splits the world: nginx-generated (502, 403, 404, 413, 503) versus app-generated (whatever the backend answered).
- The error log for that minute. The message names the path, upstream, or zone involved. Nothing in nginx fails silently that matters.
nginx -tandnginx -Tbefore you edit anything: half of "it ignores my config" is "it loads a different config."
502 Bad Gateway
Cause: the backend didn't answer — down, wrong port, or crashed. The error log is unambiguous:
[error] ... connect() failed (111: Connection refused) while connecting to upstream, ... upstream: "http://127.0.0.1:9999/", ...
Fix: read the upstream: address — is that the
port your app is actually on? Connect to the backend
directly (curl http://127.0.0.1:9001/) and make it
answer there before suspecting nginx. Variants of the
same 502: an upstream that's a Unix socket with wrong
file permissions, and a backend that accepts the
connection then dies mid-reply ("upstream prematurely
closed connection" in the log) — the log tells them
apart.
404, and the file is right there
Cause: path arithmetic. Check the error log's
open() line: it shows the exact filesystem path nginx
built. Glued-together names (.../imageslogo.txt) mean
the alias trailing-slash trap (chapter 5); a doubled
directory (.../rooted/rooted/...) means root where
you wanted alias; the right path in the wrong tree
means you're editing a config that isn't loaded
(nginx -T).
403 Forbidden
Three common causes, three log lines:
directory index of "..." is forbidden— a directory was requested, it has no index file, andautoindexis off. Fix: serve an index, or turn on autoindex deliberately (chapter 5).- A
denyrule matched — including the dotfile guard (location ~ /\. { deny all; }) doing exactly its job. - The worker user can't read the file:
open() "..." failed (13: Permission denied). The fix is file permissions for the worker's user (www-dataon Ubuntu) — every directory on the path needs execute permission, the file itself read. (This variant is described rather than captured: the lab's workers run as root for the sandbox reason in chapter 2, and root is never refused. On your machine the log line is unmistakable.)
413 Request Entity Too Large
Cause: a request body bigger than
client_max_body_size (default 1 MB — smaller than most
phone photos). Captured in the lab with the limit
deliberately at 1k and a 5 KB upload: the answer is 413
before the backend is consulted.
Fix: raise the limit in the right context —
location for the upload route, not globally, unless
you mean it. If the 413 arrives from an app behind
nginx, it may be the app's own limit wearing the same
number; the error log's silence tells you nginx didn't
generate it.
503 from your own rate limit
Cause: limit_req tripping — intended behaviour,
verify in the error log:
[error] ... limiting requests, excess: 2.580 by zone "labzone", ...
If users report it and the log agrees, raise rate= or
burst=, or narrow the location. If the log has no such
line, the 503 came from elsewhere — look upstream.
A config that won't load
Symptom: nginx -t exits 1 with an [emerg] line.
2026/10/20 20:46:46 [emerg] ... unknown directive "limit_req_zone_broken" in .../broken.conf:28
Fix: the message names the directive and the line. Unknown directive usually means a typo or a directive from a module your build doesn't load; also check for the missing semicolon one line above the one reported. And remember the running server kept its old config throughout — test against a copy when the change is large.
"The dotfiles are showing"
Symptom: /.env, /.git/config return 200 on a
public site.
Cause: everything under root is served;
nothing excluded it. Fix: the dotfile guard in
chapter 5, then reload, then re-request the path —
the fix isn't real until the 403 replaces the 200 in
front of you.
Check: for the next nginx surprise you meet, write down the status and the one error-log line before you change anything. Two lines; they will name the chapter of this guide you're in.