Writing Criteria
Every guide in this repo is written to the same standard. If you're adding or editing a guide — human or AI — hold it to these.
- Plain words, real ideas. Simple language, never dumbed-down concepts. Explain jargon on first use; collect terms in the glossary.
- No hype. No marketing adjectives, no "revolutionary," no promises about what something will do for the reader.
- Honest about limits. Say what the thing can't do. State uncertainty instead of smoothing it over. Validation notes include what remains unverified.
- No invented numbers. Benchmarks, defaults, and specs are either measured or qualified with "depends on your setup." If it wasn't tested, say so.
- Checkable. Commands are meant to be run, with "Check:" blocks showing what success looks like. Sources are linked, not asserted.
- Respect the reader's setup. Call out Windows/Mac/Linux differences. Don't assume a GPU, a budget, or prior experience.
- Safety isn't skipped. Real risks get stated plainly, not buried.
- Scoped. Each guide says what it covers and points elsewhere for the rest. No guide pretends to be the whole subject.
- Structure over prose. Tables, before/after examples, symptom → fix. Scannable beats essay.
Verification notes for this guide
Everything hands-on was run for real in a loopback lab (nginx 1.24.0, Ubuntu package; ports 8080/8082/8443 on 127.0.0.1, backends on 9001/9002), as its own instance under a lab prefix; the system nginx was installed but never started, and its config is untouched. Full transcripts are the lab's receipts.
Live-captured:
- Install (
apt-get install nginx),nginx -v, the stock/etc/nginxlayout, thesites-enabledsymlink, and the include chain in the stock main config. nginx -tsucceeding on the lab config;nginx -T's expanded dump. A deliberately typo'd copy failing-twithunknown directive "limit_req_zone_broken", exit 1.- Master/worker process shape in
ps. - Static serving: index, a plain file,
Content-Type: text/csswithmime.typesincluded; a custom 404 page served with status 404; a directory without an index answering 403 with thedirectory index of ... is forbiddenerror-log line. - Location precedence proven with five fixtures: exact
=, plain prefix, longer prefix, regex beating the longer prefix,^~suppressing the regex. Both Check predictions in chapter 6 were also run and matched. rootvsaliasarithmetic, and the alias trailing-slash trap: 404 withopen() ".../imageslogo.txt" failedin the error log.try_filesSPA fallback returning the index for a deep route.autoindexdirectory listing, and a dotfile served in the unguarded configuration ("you should never see this over HTTP").- Reverse proxying against an echo backend: control run direct
(no forwarded headers), proxied without
proxy_set_header(backend sees only nginx,Hostrewritten to the upstream), proxied with the header block (Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Protoall present). The backend's own log shows all requests arriving from 127.0.0.1, with the forwarded address only on the headered run. - Redirects:
return 301and arewrite ... permanent, each with theLocationheader captured. - TLS: self-signed certificate generated with openssl (SANs
DNS:localhost,IP:127.0.0.1), inspected withopenssl x509; curl failing untrusted (exit 60), succeeding with-k, and succeeding with--cacertand full verification;openssl s_clientnegotiating TLSv1.3; the HTTP→HTTPS redirect server answering 301 with anhttps://...:8443Location. - Failures on purpose:
proxy_passto a dead port → 502 withconnect() failed (111: Connection refused) while connecting to upstreamin the error log; a 5 KB POST againstclient_max_body_size 1k→ 413. - Upstream pool: round-robin alternating across two backends;
after the 9002 backend was stopped, all requests served by
9001, with
connect() failedandupstream server temporarily disabledlines in the error log. - Rate limiting: 12 rapid requests → 200 200 200 then 503s, a
limiting requests, excess: 2.580 by zone "labzone"error-log line, and recovery after a 2-second pause. The first fixture version (areturnin the same location) did not limit at all; that negative result is captured in the working notes and taught in chapter 11 as the phase-ordering trap. - Reload: config edited (dotfile
deny alladded, index content changed),nginx -t,nginx -s reload— master PID unchanged (13739), new content served, the previously served dotfile now answering 403. - Access-log lines in combined format and error-log contents, quoted from the lab's own log files.
Documented-only (described, not captured), with reasons:
- Public ACME/Let's Encrypt issuance and the certbot flow — needs a public domain, DNS control, and internet reachability the sandbox doesn't have. The server block it feeds is the captured one.
- The filesystem-permissions 403 (
open() ... failed (13: Permission denied)) — the lab's workers run as root because this sandbox refuses thechown()nginx performs for non-root worker users (uid 65534 and uid 33 attempts both captured,[emerg] chown(...) failed (1: Operation not permitted)); root is never permission-refused, so the variant is described and its log line quoted from nginx's documented format. On a stock Ubuntu install workers run aswww-dataand this 403 is the ordinary one. - NGINX Plus features (active health checks) — named as commercial, not exercised.
- Production hardening checklists — out of scope by design (README says so); the guide covers the two exposures it could demonstrate (directory listing, dotfiles).
- Windows/macOS — named only; nginx there is a different install story and wasn't available.
- systemd management of the system instance (
systemctl reload nginx) is stated as the package's standard behaviour; the lab deliberately never started the system service (the package's install was blocked from auto-starting by policy-rc.d, which the transcript shows).