Cheat Sheet

nginx Directives

nginx is the web server and reverse proxy in front of most Linux deployments. This sheet covers the directives you edit most, which block each one is allowed in, and the commands that test and reload a config safely.

Last updated September 11, 2026

Test and Reload

Never restart when a reload will do. Reload keeps existing connections open and falls back to the old config if the new one is broken.

CommandWhat it does
Test config syntax, prints the failing file and line
Test, then print the full merged config from all includes
Reload config without dropping connections
Same reload, via systemd
Full restart, drops connections; needed for new ports
Is it running, and the last few log lines
Start on boot
Version ( adds modules and compile-time paths)
Stop after current requests finish ( kills immediately)
Start with a specific config file

The safe edit loop, in one line:

sudo nginx -t && sudo systemctl reload nginx

Server Blocks

One block per site (virtual host). nginx picks the block by matching port, then : exact name, longest wildcard, first matching regex, then the .

DirectiveWhat it does
Accept plain HTTP on port 80
Accept HTTPS (pair with on nginx 1.25+)
Same, on IPv6
Catch requests that match no
Hostnames this block answers for
Wildcard subdomains
Regex name (starts with )
Placeholder, used with
Document root for the whole site
Files to try for directory requests

A minimal static site:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

The nginx config generator builds a full server block from a form.

Location Matching

File order does not decide prefix matches. nginx checks first, then the longest prefix, then regexes in file order; the first regex hit wins, and only if none match does the longest prefix apply.

PatternHow it matches
Exact URI only, checked first
Prefix; if it is the longest prefix, skip regex checks
Regex, case-sensitive
Regex, case-insensitive
Plain prefix
Prefix that matches everything, the fallback
Serve the file, else the directory, else hand off
Serve the file or return 404

Reverse Proxy

forwards requests to an app server. Pass the headers below or the app sees every request as coming from 127.0.0.1 over HTTP.

DirectiveWhat it does
Forward to the app, URI passed through unchanged
Trailing slash: the location prefix is stripped first
Give the app the original hostname
The visitor's IP
The full client IP chain
http or https, so the app builds correct URLs
Needed for websockets and upstream keepalive
Stream responses through (SSE, long polling)

A complete proxy block, websockets included:

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Upstream

An block names a group of backend servers for . It lives directly inside , next to your server blocks, not inside one.

DirectiveWhat it does
A backend; round-robin between servers is the default
Gets twice the traffic
Used only when the others are down
Temporarily out of rotation
Mark dead after 3 failures, retry after 30s
Send to the backend with fewest active connections
Same client IP always hits the same backend (sticky)
Pool of idle connections to the backends
upstream app {
    least_conn;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    keepalive 32;
}

root vs alias

appends the full URI to the path; replaces the location prefix with the path.

location /img/ { root /var/www; }
# /img/a.png  ->  /var/www/img/a.png

location /img/ { alias /var/www/pics/; }
# /img/a.png  ->  /var/www/pics/a.png
RuleDetail
Default to Set it once at level; locations inherit it
is location-only is valid in , , and
Match the slashesIf the location ends in , the path must too
Never bothAn in a location overrides the inherited

Redirects and Rewrites

For a plain redirect, is simpler and faster than .

DirectiveWhat it does
Permanent redirect, keeps the path and query
Temporary redirect
Answer with a status code, no redirect
Regex redirect with a captured path (301)
Same, temporary (302)
Internal rewrite, re-runs location matching
Internal rewrite, stays in the current location

The classic www and HTTP cleanup, as separate small server blocks:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    return 301 https://example.com$request_uri;
}

SSL

DirectiveWhat it does
Serve TLS on this block
Enable HTTP/2 (nginx 1.25.1+; the old parameter is deprecated)
Full chain, not just the leaf cert
Private key
Drop the legacy protocols
Resume sessions, saves handshakes
How long sessions stay resumable
HSTS: browsers stop trying HTTP
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
}

Verify what a live server actually presents with the SSL checker, and inspect a cert file with the certificate decoder.

Gzip and Caching

DirectiveWhat it does
Compress responses ( is always included)
What else to compress
Skip tiny responses, not worth the CPU
1-9; above 6 costs CPU for little gain
Sets and
Tell browsers not to cache
Full manual control instead of
Define a cache for proxied responses ( context)
Use it in a location

Long cache for fingerprinted static assets:

location ~* \.(css|js|png|jpg|svg|woff2)$ {
    expires 365d;
    add_header Cache-Control "public, immutable";
}

map

sets one variable from the value of another, and belongs directly inside . It replaces most chains.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ""      close;
}

map $host $backend {
    hostnames;
    default          http://127.0.0.1:3000;
    api.example.com  http://127.0.0.1:4000;
    *.example.com    http://127.0.0.1:3000;
}
SyntaxWhat it does
Fallback when nothing matches
Enables style wildcards on the keys
Regex key ( for case-insensitive)
Load key-value pairs from a file

Variables

Variables are read anywhere a directive takes a value, and any request header is available as with dashes turned into underscores.

Variable or directiveWhat it holds
Hostname from the request line, or the header
Current URI after rewrites, normalized, no query string
Original URI exactly as sent, query string included
The query string ( for one parameter)
Client IP as nginx sees it
or
, , and so on
, Response code and duration, for
Any request header; reads response headers
Define your own variable (, )
Set a variable from the client IP ( context)
DNS resolver; required when a URL contains a variable

Includes and File Locations

Path or directiveWhat it is
Main config; everything else is included from here
Auto-included site configs (RHEL, Alpine, official packages)
Site configs on Debian/Ubuntu, inactive until linked
Symlinks to the active site configs
File-extension to Content-Type table
Reuse a shared fragment (path relative to )
Enable a Debian-style site, then reload
Disable it (the file in sites-available stays)
Find which included file defines what

Logs and Error Pages

DirectiveWhat it does
One line per request
Silence a noisy location (health checks)
Levels: , , , , ,
Custom line format, used via
Serve a custom page for a status code
One page for several codes
The page can be served only by , not by URL
Let handle error statuses that came from the backend
Watch errors live; the first stop for any 500

Startup failures land in the systemd journal rather than the error log; see the journalctl cheat sheet for .

Allow, Deny and Auth

Rules are checked top to bottom and the first match wins.

DirectiveWhat it does
Permit a network
Permit one IP
Block everyone else; goes last
Ask for a username and password
Where the credentials live
Let a request pass on allowed IP or valid password
location /admin/ {
    satisfy any;
    allow 192.168.1.0/24;
    deny  all;
    auth_basic "Admin";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

Generate the password file with the htpasswd generator.

Timeouts and Limits

DirectiveWhat it does
How long an idle client connection stays open
Max upload size; the default 1m is behind most 413 errors
How long to wait for the request body
How long to wait on a stalled client while responding
Time to establish the backend connection
Max backend silence; raise for slow APIs throwing 504s
Time to write the request to the backend
Connections per worker ( block)

Stream

proxies raw TCP and UDP (databases, SMTP, game servers). It sits beside in , never inside it, which is why pasting one into a file returns "stream directive is not allowed here".

stream {
    upstream db {
        server 10.0.0.10:5432;
    }

    server {
        listen 5432;
        proxy_pass db;
    }
}
DirectiveWhat it does
TCP port to accept on ( for UDP)
Backend or upstream name; no scheme, no URI path
How long a connection may sit idle
Time allowed to reach the backend
Read the SNI hostname without decrypting, then route on
Send the PROXY protocol header so the backend sees the real client IP
has no access log unless you add with a format

Global and Events

The top of , outside every block. Simple directives here end in a semicolon; block directives take braces and no semicolon.

DirectiveWhat it does
OS user the worker processes run as ( on RHEL)
One worker per CPU core
Raise the file descriptor limit per worker
Where the master process writes its PID
Stay in the foreground, which is what containers want
Load the dynamic modules installed as packages
Connection limit per worker; is its own block
Everything web related goes in here
TCP and UDP proxying, a sibling of

Modules

means nginx does not recognize the name at all. Either it is misspelled, or the module that provides it was never built into this binary or loaded.

Command or directiveWhat it does
Print the build's compile flags and every module
Load a dynamic module; main context, before
Debian and Ubuntu: one small conf file per enabled module
Adds the module and its line
Same for ; the pattern is
RHEL and Fedora equivalent ()
, , Third-party; they need a build that ships them (OpenResty for lua)

Context Rules

Every directive is valid only in certain blocks; using the right directive in the wrong block is the "directive is not allowed here" error. Files under and are included inside , so they can hold , , and blocks.

DirectiveAllowed contexts
only
, or nested in another
, only, never inside
, only
only
only
, , , , (set once, inherited down)
, ,
, only, never
only
, ,
only
only
, , Main context only, siblings of each other
, , , Main context, top of

Order and Inheritance

RuleDetail
Location matching first, then the longest prefix, then regexes in file order; on the winning prefix skips the regex step
matchingExact name, then , then , then regex in file order, then
Inheritance, , and friends pass down from to to until a child sets them again
No mergingOne or in a child block discards every inherited one, not just the matching name
and Read top to bottom, first match wins
Every rule in the block runs in order; restarts location matching, stops the block
DuplicatesMost directives may appear once per block, or nginx returns "directive is duplicate"; put all hostnames on one line. , , , , and may repeat

Gotchas

  • The trailing slash on changes the URI: with a URI part (), the matched location prefix is stripped; without one (), the path passes through untouched.
  • in a location throws away every inherited from and . Repeat the parents' headers or set them all at one level.
  • Prefer and over . Inside a location, combined with anything beyond or behaves in famously surprising ways.
  • If no block has , the first server block for that port silently answers every unmatched hostname, including raw IP requests.
  • When seems to do nothing, check which server block actually answered (), whether the sits in a location the request never matched, and whether the worker user can traverse every parent directory. The error log prints the exact path nginx tried to open.
  • ignores statuses that came from a backend until you add , and the page it points at must be servable by the same server block.

nginx FAQ

What are nginx directives?

A directive is one instruction in the config. Simple directives are a name, its arguments, and a semicolon (root /var/www;). Block directives take braces instead and hold other directives inside them (server { ... }), which is why they take no semicolon. The block a directive sits in is its context, and most directives are valid in only some contexts. Values set in an outer context pass down to the blocks inside it until one of them sets the same directive again.

Why does nginx say a directive is not allowed here?

The directive is real but sits in the wrong block. Every directive is valid only in certain contexts: alias only inside location, proxy_pass inside location, upstream and map only directly inside http, listen and server_name only inside server, events and stream only in the main context beside http. Files under conf.d/ and sites-enabled/ are included inside the http block, so they can hold server, upstream, and map blocks, but a location pasted at the top of such a file, outside any server block, triggers exactly this error. nginx -t names the file and line.

What does nginx unknown directive mean?

Unknown directive means nginx does not recognize the name at all, so the module that provides it is not in this build. It is the usual answer for stream, brotli, rtmp, geoip2, and the lua directives. Run nginx -V to see what was compiled in. On Debian and Ubuntu the extras ship as separate packages (apt install libnginx-mod-stream, libnginx-mod-http-geoip2) that drop a load_module line into /etc/nginx/modules-enabled/; on RHEL and Fedora they are the nginx-mod-* packages. If the name is misspelled or the module is third-party, no package will help and you need a build that includes it.

What order does nginx apply directives in?

File order rarely decides anything. For locations, nginx checks = first, then the longest matching prefix, then regex locations (~ and *) in file order, taking the first hit; only when no regex matches does the longest prefix win, and a ^ on that prefix skips the regex step. server_name matching goes exact name, then .example.com, then example., then regex, then default_server. Inside a block, allow and deny are read top to bottom with first match winning, and rewrite rules all run in sequence.

Why is the nginx root directive not working?

Three things cause almost all of it. root appends the whole request URI to the path while alias replaces the matched location prefix, so location /img/ { root /var/www; } reads /var/www/img/a.png and the same location with alias /var/www/pics/ reads /var/www/pics/a.png. A root set inside one location does not apply to requests that matched a different location, so set it once at server level. And the worker user (www-data or nginx) needs execute permission on every parent directory, not just the files. nginx -T shows the root nginx actually ended up with, and the error log prints the full path it tried to open.

Related cheat sheets