This cheatsheet lists the nginx directives you reach for most when writing a server block: listening, routing with location, serving files, proxying, compression, headers and limits. It is for developers who edit nginx.conf by hand. It gives the default value and allowed context for each directive, which the official docs spread across many module pages. The configs below were run on nginx 1.24.0 and checked with curl.
| Directive | Default | Context | Purpose |
|---|---|---|---|
| `worker_processes` | `1` | main | Number of worker processes. `auto` matches CPU cores |
| `worker_connections` | `512` | events | Maximum simultaneous connections per worker |
| `error_log` | `logs/error.log error` | main, http, server, location | Error log path and minimum level |
| `access_log` | `logs/access.log combined` | http, server, location | Request log. `access_log off;` disables it |
| `listen` | `*:80` as root, `*:8000` otherwise | server | Address and port, plus `ssl`, `default_server` |
| `server_name` | `""` | server | Host names this server answers |
| `root` | `html` | http, server, location | Directory that maps to the URL path |
| `index` | `index.html` | http, server, location | Files tried for a directory request |
| `include` | none | any | Pull in another config file or glob |
| Directive | Context | Purpose |
|---|---|---|
| `location` | server, location | Match a request path and apply settings |
| `try_files file ... uri` | server, location | Use the first file that exists, else fall back to a URI or `=code` |
| `return code [text]` | server, location, if | Send a response or redirect immediately |
| `rewrite regex replacement [flag]` | server, location, if | Change the URI with a regular expression |
| `error_page code ... uri` | http, server, location | Serve a custom page for a status code |
| `alias path` | location | Replace the matched location prefix with a path |
| `add_header name value [always]` | http, server, location | Add a response header |
| Form | Match type | Priority |
|---|---|---|
| `location = /path` | Exact | First. Search stops on a match |
| `location ^~ /path/` | Prefix that skips regex | Second. Regex locations are not checked |
| `location ~ regex` | Case-sensitive regex | Checked in file order, first match wins |
| `location ~* regex` | Case-insensitive regex | Same list as `~` |
| `location /path/` | Plain prefix | Longest prefix is remembered, used if no regex matches |
| Directive | Default | Purpose |
|---|---|---|
| `proxy_pass URL` | none | Forward the request to a backend |
| `proxy_set_header field value` | `Host $proxy_host`, `Connection close` | Set a header sent to the backend |
| `proxy_connect_timeout` | `60s` | Time to establish the backend connection |
| `proxy_send_timeout` | `60s` | Time between two writes to the backend |
| `proxy_read_timeout` | `60s` | Time between two reads from the backend |
| `gzip` | `off` | Turn on response compression |
| `gzip_types` | `text/html` | Extra MIME types to compress. `text/html` is always compressed |
| `gzip_min_length` | `20` | Smallest response length, in bytes, that is compressed |
| `gzip_comp_level` | `1` | Compression level from 1 to 9 |
| `gzip_vary` | `off` | Add `Vary: Accept-Encoding` |
| Directive | Default | Purpose |
|---|---|---|
| `client_max_body_size` | `1m` | Largest request body. Larger gets 413 |
| `keepalive_timeout` | `75s` | How long an idle client connection stays open |
| `sendfile` | `off` | Use the kernel `sendfile()` call for static files |
| `server_tokens` | `on` | Show the nginx version in `Server` and error pages |
| `ssl_certificate` | none | Path to the certificate file |
| `ssl_certificate_key` | none | Path to the private key |
| `ssl_protocols` | `TLSv1.2 TLSv1.3` | Allowed TLS versions |
nginx -t -c /tmp/ng/nginx.conf
nginx: the configuration file /tmp/ng/nginx.conf syntax is ok
nginx: configuration file /tmp/ng/nginx.conf test is successful
Always run nginx -t before nginx -s reload. It parses the file and reports the first error with a file name and line number, without touching the running server. Without -c, it tests the default config path compiled into your build, which on many Linux packages is /etc/nginx/nginx.conf.
server {
listen 8088;
server_name example.test;
root /tmp/ng/www;
index index.html;
location / { try_files $uri $uri/ =404; }
location = /exact { return 200 "exact\n"; }
location ^~ /static/ { return 200 "prefix-static\n"; }
location ~* \.txt$ { add_header X-Kind text always; try_files $uri =404; }
}
GET / -> 200 home
GET /exact -> 200 exact
GET /static/x -> 200 prefix-static
GET /hello.txt -> 200, header X-Kind: text
GET /nope -> 404 Not Found
Each line was produced by a real curl against this config. When several locations could match, the exact match wins first, then a ^~ prefix, then regexes in file order, then the longest plain prefix. That is why /static/x hit the prefix block and /hello.txt hit the regex block. try_files $uri $uri/ =404 checks for a file, then a directory, then returns 404 instead of looping.
location /old { return 301 /new; }
HTTP/1.1 301 Moved Permanently
Location: http://example.test:8088/new
Prefer return over rewrite for simple redirects. It states the status code and target directly, with no regular expression to debug. Note that the Location header became an absolute URL with the server name and port.
upstream backend { server 127.0.0.1:9001; }
location /api/ { proxy_pass http://backend/; proxy_set_header Host $host; }
GET /api/users?id=1 -> backend saw /users?id=1 host=example.test
A proxy_pass with a URI (the trailing /) replaces the part of the path that matched the location. Remove the trailing slash and the backend receives /api/users?id=1 unchanged.
gzip on;
gzip_types text/plain;
Content-Encoding: gzip
51
3001
A 3,001-byte repetitive text file came back as 51 bytes when the client sent Accept-Encoding: gzip, and as 3,001 bytes without it. Real files compress less, but the header and the size drop are what to check.
client_max_body_size 1k;
413
2026/10/05 09:24:33 [error] 8186#8186: *1 client intended to send too large body: 3001 bytes, client: 127.0.0.1, server: example.test, request: "POST /hello.txt HTTP/1.1", host: "localhost:8088"
The first line is the status code curl received. The second is the entry in error.log.
nginx -t prints [emerg] unexpected "}" in /tmp/ng/bad.conf:18 and test failed. In that run the cause was return 301 /new } with no semicolon. Fix the line named in the message.client_max_body_size is 1m. Raise it in the right context (http, server or location) and reload.proxy_pass: proxy_pass http://backend/; rewrites the path, while proxy_pass http://backend; passes it unchanged. One character changes which URL the backend sees.^~ on the prefix if a regex such as \.php$ should not take over.add_header disappearing: Per the nginx docs, headers are inherited from the previous level only if the current level defines no add_header of its own. Repeat every header in a location that adds one. Without always, headers are not added to every response code: in a test, a 404 carried neither the http-level X-Top nor a location-level header, while a 200 carried X-Top.proxy_read_timeout 60s and the client sees a 504. Raise it for that location only.gzip on; alone compresses only text/html. List other types such as text/plain or application/json in gzip_types.server_tokens on (the default) the Server header and error pages show the nginx version. server_tokens off; reduced the header to Server: nginx in this run.alias and root mixed up: root appends the whole URI to the path, while alias replaces the location prefix. location /bad/ { alias /tmp/ng/al; } returned 404 for /bad/x.txt because nginx looked for /tmp/ng/alx.txt, while location /files/ { alias /tmp/ng/al/; } served the file. Keep the trailing slash on both sides..conf files that route to local Node.js or Docker ports, with SSL, WebSocket and gzip supportadd_headeradd_header and proxy_set_headeradd_header ... always