Skip to content

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

  1. The status code splits the world: nginx-generated (502, 403, 404, 413, 503) versus app-generated (whatever the backend answered).
  2. The error log for that minute. The message names the path, upstream, or zone involved. Nothing in nginx fails silently that matters.
  3. nginx -t and nginx -T before 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, and autoindex is off. Fix: serve an index, or turn on autoindex deliberately (chapter 5).
  • A deny rule 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-data on 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.