Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

2.1 The Configuration Language

The grammar of everything that follows. Short lesson, permanent payoff.

Levels: Beginner ~30 min read

Basic

What you will learn

  1. Directives, blocks, and contexts — the three atoms of NGINX config.

  2. The six syntax rules that cover 99% of what you'll ever write.

  3. Where each context may live, and what may live in each context.

  4. How errors actually present themselves (three real ones, translated once).

  5. Units, flags, and the value syntax you'll meet forever ( on/off, 1m, 10k, 30s, off).


1. The three atoms

An NGINX configuration is a tree. Everything you will ever write is one of three things:

text
directive   →   worker_processes  auto;              (name, value(s), semicolon)
block       →   events { … }                         (a context: braces)
directive
with block  →   limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
                (some directives take both a body and a header line)
  • A directive is name value; — the semicolon is mandatory; NGINX's parser is famously unforgiving.

  • A block { … } opens a context. The context determines what the directives inside mean and where their values flow.

  • The file's top level (outside any block) is the main context.

The contexts you will use daily, in nesting order:

text
main
├── events { }                 — connection-level (per worker) settings
├── http { }                   — everything HTTP; only ONE allowed
│   ├── server { }             — one virtual host (listen + server_name)
│   │   └── location { }       — "if the URI looks like this…"
│   ├── upstream { }           — a named backend pool (Module 5)
│   └── map / geo / limit_req_zone / …   — http-level variables & zones
└── stream { }                 — Layer-4 TCP/UDP (Module 10)
    └── server { }

2. The six syntax rules

Rule 1 — semicolons end every directive. Even the last one in a block. The classic failure: a missing ; makes the parser glue the next line into the value, and the error message points at the wrong line.

Rule 2 — braces delimit contexts, and their contents are indented by convention only. NGINX ignores whitespace inside lines but not structure.

Rule 3 — values are space-separated words.

nginx
listen 8200;                          # one value
add_header X-Server alpha always;     # three values
root /var/www/site;                   # one value (no quotes needed)
return 200 "matched: exact\n";        # one value, quoted because of spaces/\n

Quoting is required only when a value contains spaces or special characters — but professional configs quote any header value as a habit.

Rule 4 — # starts a comment. To end of line. There are no block comments. (A # inside a quoted string is literal.)

Rule 5 — variables are $name , evaluated per request (except in a few string-literal places like log_format… which is exactly where they are evaluated per request — Lesson 2.4). Config files cannot do arithmetic or loops; map (Lesson 2.4) and njs (Module 15) are the escape hatches.

Rule 6 — some directives are arrays (repeatable), most are not. Repeating a singleton directive in the same context is an error ( "root" directive is duplicate); repeating an array directive ( add_header, listen, server_name, include, access_log, …) appends. This distinction becomes the inheritance rule in Lesson 2.5.

3. What lives where — the context map

Every directive has a list of legal contexts. Violations fail at parse time with a clear message. The ones you'll actually consult:

Directive

main

http

server

location

worker_processes

✅

error_log

✅

✅

✅

✅

access_log

✅

✅

✅

root / alias

✅

✅

✅

listen / server_name

✅

location

✅

✅ (nested)

return / rewrite

✅

✅

✅

proxy_pass

✅

✅

✅

limit_req_zone

✅

limit_req

✅

✅

✅

worker_connections

events only

Two asymmetries worth internalizing now:

  • listen and server_name are server-only — they define what a server is; there's nothing to inherit.

  • Zone- creators ( limit_req_zone, map, proxy_cache_path) are http-only — they allocate shared memory at config load; consumers ( limit_req, the $variable, proxy_cache) are the per-request users.

4. Three real errors, read like a native

These three failures are real — each occurred while building a working config. Each error message is worth translating once, slowly:

(a) Wrong number of arguments:

bash
2026/09/22 20:24:24 [emerg] 2701#2701: invalid number of arguments in "return" \
  directive in …/conf/nginx.conf:117

Cause: return 200 "a\n" "b\n"; — the author assumed string concatenation. NGINX has no implicit concatenation; each quoted string is a separate argument. Fix: one string. Lesson: the parser counts arguments strictly; line 117 is where the value ended, not where your intent ended.

(b) A compiled-in default leaking into a test run:

bash
nginx: the configuration file … syntax is ok
2026/09/22 20:24:37 [emerg] 2768#2768: open() "/var/log/nginx/access.log" \
  failed (2: No such file or directory)

Cause: the config set access_log in servers but not at http level. The http-level default — the compiled --http-log-path=/var/log/nginx/access.log (Lesson 1.3) — fired first, and on a prefix-isolated instance that path doesn't exist. nginx -t opens log files as part of testing, so this is a test failure, not just noise. Fix: access_log logs/access.log main; at http level. Lesson: "syntax is ok" ≠ "config is sound" — -t also exercises files and directories.

(c) A directive in the wrong context:

bash
[emerg] "location" directive is not allowed here in …/conf/nginx.conf:88

Self-explanatory once you have §3's table — and note it's [emerg], i.e. fatal, reported with the exact line.

5. Units, flags, and value dialects

NGINX has small value languages inside the big one. Learn them once:

Kind

Examples

Notes

flags

on / off

literal words; off as a filename-ish value also disables ( access_log off;)

time

30s 5m 2h 7d 500ms

no space; suffixes compose ( 1h30m)

size

1k 10m 1g

k=1024, m=1024² — not SI units

rate

10r/s 600r/m

requests per second/minute (Module 7)

offsets

rate=100 in limit_req

dimensionless "requests"

lists

ssl_protocols TLSv1.2 TLSv1.3;

space-separated multiple values

"…"

return 200 "ok\n";

quoted when spaces/escapes needed

Time and size units trip people in reviews, not in errors: 1m of memory is a MiB; 10m of keepalive_timeout is ten minutes. When a directive accepts both (" 10m or 1h"), the parser is unit-aware; when a value is off, it usually means "disable this whole feature".

Try it yourself

Everything here is one vi away from being tested — your nginx.conf is a living grammar playground:

  1. Break Rule 1 on purpose. In /etc/nginx/nginx.conf, delete the semicolon after worker_processes auto — the parser will report the error on the following line. Observe, restore, re-verify:

bash
   nginx -t
  1. Reproduce error (b). Comment out the http-level access_log line, run -t, read the message. Restore it.

  2. Watch arguments get counted. Change a return 200 "…"; into return 200 "a" "b"; and read the exact error from §4(a).

  3. Confirm context policing. Move listen 8200; into the http {} level and enjoy the parser's refusal. Restore.

  4. Re-verify when done — every experiment must end with a config that passes nginx -t again:

Q&A

Q1. Why does NGINX report a missing semicolon on the wrong line?

Answer

The parser is greedy across newlines — it happily continues collecting the directive's value words from the next line, and only fails when it hits a token that can't be part of the (now over-long) directive. The reported line is where the grammar broke, not where your intent did.

Q2. keepalive_timeout 65; — 65 what? And client_body_buffer_size 16k — 16 of what?

Answer

65 seconds (plain numbers default to seconds for time directives) and 16 KiB (k = 1024). Both defaults come from the same "convenient small-number" philosophy; both are quoted from memory throughout the series.

Q3. You see limit_req_zone inside a server block. Will NGINX start?

Answer

No — [emerg] "limit_req_zone" directive is not allowed here. Zone creators are http-only because the master allocates their shared memory once at config load; a per-server zone would be meaningless before any server exists. (Module 7 makes this architectural reason concrete.)

Q4. True/false: include is a context.

Answer

False — include is an array directive (repeatable) whose value is a glob. It textually splices other files in at exactly its own context level. Lesson 2.6 is devoted to exploiting that.

Takeaways

  • Three atoms: directive, block, context; one tree walked per request.

  • Six syntax rules cover nearly everything; the parser is strict, line numbers are approximate, nginx -t is your only sane workflow.

  • Every directive has legal contexts; creators of shared state are http-only, identity directives ( listen/ server_name) are server-only.

  • "Syntax is ok" is weaker than "test is successful" — -t also opens logs and creates temp dirs.

  • Learn the value dialects (flags, s/m/h, k/m/g, r/s) once; they are uniform across all ~700 directives.

Was this page helpful?

Mark this learning resource complete to track your learning progress.