Nginx Config Cheat Sheet
Common Nginx config: server, location, proxy_pass, upstream, ssl, gzip, rewrite directives and snippets, with notes for ops, deployment and troubleshooting.
How It Works
Nginx is an event-driven, high-performance web server and reverse proxy. Config is hierarchical: an http block holds several servers (virtual hosts), each with locations (matched by URI prefix or regex) where directives apply closest-wins.
Common roles: reverse proxy (proxy_pass forwards to backends), load balancing (upstream + strategies), static file serving, TLS termination, gzip and caching. The order of location matching (^~, ~, ~*) strongly affects routing and must be designed with care.
The two things worth getting right first: write out the server_name and location matching relationships and confirm them, because most "config does not apply" cases come from ordering or regex precedence rather than a miswritten directive; and give each location a distinct access_log tag so you can group status codes by path during debugging instead of grepping line by line. Finally, always run nginx -t before reload — a syntax error makes reload keep the old configuration without reporting an error, which easily leads you to believe the new config is live.
The combination of buffering and timeouts deserves attention too: proxy_buffering decides whether responses are written to disk before reaching the client; disabling it lowers time-to-first-byte but shifts concurrency pressure onto the backend. And proxy_read_timeout versus send_timeout express different directions of waiting (reading from upstream versus writing to client), so adjusting one rarely fixes a timeout. When you see a 504, confirm it is the upstream read timing out, not the client write.
When to Use This Table
This table is for debugging "I changed the config and nothing happened" and odd reverse-proxy or caching behaviour. nginx directives live in five contexts — main, events, http, server, location — and the scoping and override rules are the source of most confusion.
When not to consult it: you are designing an application-level balancing or health-check strategy. nginx provides the primitives, but whether the strategy is good depends on the domain; the snippet is only the means.
Rule of thumb: when a directive does not apply, check the context nesting and whether a higher-priority location matched first; when a proxy misbehaves, check the trailing-slash semantics of proxy_pass. Together these account for most real incidents.
Mind that the position of an include decides the context: inside the http block a snippet can use http-level directives; inside a server block those same directives are out of scope. The same file has a different usable directive set depending on where it goes.
Boundaries and Common Pitfalls
The trailing slash on proxy_pass decides whether the URI is replaced
With location /api/ and proxy_pass http://backend/ (trailing slash), a request to /api/users is rewritten to /users. With proxy_pass http://backend (no slash), the same request is forwarded as /api/users intact. This is the most common root cause of reverse-proxy 404s: the request reaches the backend with the wrong path. When debugging a batch, align the slashes first, then look at anything else.
location matching has priority, and exact matches win first
Precedence runs: exact (=), then prefix with ^~, then regex (~ / ~*), then longest plain prefix. A bare location / therefore captures everything not claimed by a more specific rule, and a mistaken regex can push requests that should hit static files into the proxy. This is why "I added a location and nothing changed" is so common: the new rule was pre-empted by a higher-priority one.
try_files and error_page can mask the real 404
An error_page 404 /404.html after try_files ... =404 renders both a genuine upstream 404 and a locally missing file as the same custom page, so the two become indistinguishable in logs. Debug by reading error_log and access_log together: a 404 status in access_log with no corresponding upstream entry is the signature of a local miss.
Inheritance and duplicate addition of header directives
add_header appends rather than overriding, so multi-level configs can end up with two X-Frame-Options or two Cache-Control headers — and duplicate Cache-Control makes caching behaviour unpredictable. nginx 1.29 introduced add_header_inherit to merge child headers; before that the common practice is to declare a variable and inject it via a regex include, rather than writing the header at each level.
How This Relates to Neighbours
For status code semantics (the difference between 502 and 504, how 304 arises), use the in-site HTTP Status Codes page; this one covers only how nginx handles them.
Certificates and TLS setup belong to the in-site certificate tools and reference; the HTTPS snippet here covers the common path, not certificate-chain debugging.
Caching behaviour behind a proxy has to be read together with Cache-Control and ETag; this page does not cover HTTP caching semantics itself.
System-level troubleshooting commands (ss/lsof for ports, journalctl for startup logs) belong to operations, not to a cheat sheet.
Practical Snippet
Key points: (1) the proxy_pass trailing slash pairs with the location prefix, otherwise /api/users is forwarded verbatim and 404s; (2) regex locations outrank plain prefixes, so a mistyped static-asset rule can divert requests into the proxy; (3) add_header appends rather than replaces, so multi-level configs produce duplicate headers — use a variable plus a regex include; (4) an SPA needs try_files falling back to index.html or deep-link refreshes 404.
# 反向代理:注意两个 location 前缀与 proxy_pass 的斜杠要「成对」
location /api/ {
# 末尾 / → /api/users 转发为 /users
proxy_pass http://backend_upstream/;
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;
# 上游先扛 3 秒,再交给 nginx 重试
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
# 用变量 + 正则 include 统一注入头,避免 add_header 多层重复
set $CSP_DEFAULT "default-src 'self'";
include /etc/nginx/snippets/csp.conf;
}
# 静态资源:长缓存 + 内容哈希文件名
location ~* \.(?:js|css|woff2|png|svg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable" always;
access_log off; # 高频静态资源不写 access_log
try_files $uri =404;
}
# 单页应用:不存在则回退到 index.html(SPA 路由必需)
location / {
try_files $uri $uri/ /index.html;
}Practical Notes
- location matching priority is not file orderExact = wins first, then the ^~ prefix, then regex ~, then ordinary prefixes. A plain prefix written earlier is therefore overridden by a later regex. When a rule "does not apply", check priority before rewriting it.
- A trailing slash on proxy_pass decides whether the path is replacedproxy_pass http://up/ drops the location prefix while proxy_pass http://up keeps it. One missing slash produces duplicated prefixes like /api/api/xxx, the classic mistake in this configuration.
- A static location without try_files returns 404Deep routes of a single-page app have no file on disk, so try_files $uri $uri/ /index.html is required. Without it a refresh returns 404 while in-site navigation works, which makes the bug look intermittent.
Data Sources
- nginx documentation (nginx.org/en/docs)
- Mozilla SSL Configuration Generator
Codes, names and definitions above come from public specifications; we claim no originality over the specification data itself. The arrangement, Chinese annotations and practical notes on this page are written by us.
| Directive / Snippet | Description |
|---|---|
| listen 80; | Listen on a port (443 ssl for HTTPS) |
| server_name example.com; | Virtual host domain name |
| root /var/www; | Document root directory |
| index index.html; | Default index file |
| location / { } | Prefix path match block |
| location = /api { } | Exact match (highest priority) |
| location ~ \.php$ { } | Regex match (case-sensitive) |
| proxy_pass http://backend; | Reverse proxy to upstream |
| proxy_set_header Host $host; | Forward the original Host header |
| upstream backend { } | Define a load-balancing upstream group |
| try_files $uri $uri/ =404; | Try files in order, else 404 |
| rewrite ^/old /new permanent; | 301 rewrite redirect |
| return 301 https://$host$request_uri; | Force HTTP to HTTPS |
| ssl_certificate /path/fullchain.pem; | SSL certificate chain path |
| ssl_certificate_key /path/privkey.pem; | SSL private key path |
| gzip on; | Enable gzip response compression |
| client_max_body_size 10m; | Limit request body size |
| auth_basic "Restricted"; | Enable basic auth prompt |
| add_header Cache-Control "max-age=3600"; | Set cache-control response header |
| access_log /var/log/nginx/access.log; | Access log path |
| error_log /var/log/nginx/error.log; | Error log path |
| nginx -t | Test config file syntax |
| nginx -s reload | Reload config gracefully |
| nginx -s stop | Stop the service quickly |
Frequently Asked Questions
How do I configure an Nginx reverse proxy?
Inside a server block, match a path with location and forward it with proxy_pass http://backend;. Usually also set proxy_set_header Host $host; and proxy_set_header X-Real-IP $remote_addr; to pass client info; the backend can be an upstream group for load balancing.
How do I force all HTTP traffic to HTTPS?
Use a server block listening on 80 with return 301 https://$host$request_uri; to 301-redirect plaintext requests to the same host over HTTPS; the HTTPS server block then sets ssl_certificate and ssl_certificate_key.
How do I set up load balancing with upstream?
Define upstream backend { server 10.0.0.1:8080; server 10.0.0.2:8080; } in the http block (round-robin by default). Add weight= for weighting or ip_hash; for sticky sessions, then reference it via proxy_pass http://backend;.
How do I apply config changes?
First run nginx -t to validate syntax, then nginx -s reload to reload gracefully (existing connections are not dropped); if Nginx is not running, start it with nginx. Reload avoids traffic loss compared to stop/start.