Overview
Nginx config is a tree: http holds server blocks, which hold location blocks, and directives inherit downward unless overridden. Validate with nginx -t (nginx -T also dumps the merged config) and apply with nginx -s reload, which does not drop connections. Security headers are covered in cloudflare-security-headers.
Contexts
Put directives in the narrowest context that needs them.
| Block | Holds |
|---|---|
http {} | Global settings, gzip, upstream pools, include mime.types. |
upstream {} | Backend server lines and keepalive. |
server {} | listen, server_name, TLS, root, location blocks. |
location {} | proxy_pass, try_files, return, caching, headers. |
Location matching
Nginx picks the location in this order.
| Order | Form | Rule |
|---|---|---|
| 1 | location = /path | Exact match wins immediately. |
| 2 | location ^~ /path | If it is the longest matching prefix, skip regex checks. |
| 3 | location ~ re, ~* re | Regexes (case-sensitive, case-insensitive) in file order; first match wins. |
| 4 | location /path | Longest matching prefix, used when no regex matched. |
Common patterns
Use prefix locations by default and regexes only when you must.
upstream api_backend { server 127.0.0.1:3000; keepalive 32; }
server {
listen 443 ssl;
http2 on; # replaces "listen 443 ssl http2" (nginx 1.25.1+)
server_name example.com;
location /api/ {
proxy_pass http://api_backend/; # trailing slash strips /api/
proxy_http_version 1.1;
proxy_set_header Connection ""; # keepalive to the upstream
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ^~ /static/ { root /var/www; expires 30d; }
location / { # SPA fallback
root /var/www/html;
try_files $uri $uri/ /index.html;
}
}
server { listen 80; server_name example.com; return 301 https://$host$request_uri; }| Directive | Use |
|---|---|
return 301 /new; | Redirect without regex. |
try_files $uri =404; | Last argument is the fallback: a status or a named location. |
root vs alias | root appends the full URI; alias replaces the location prefix. |
expires 30d; | Sets Cache-Control and Expires; do not add a second Cache-Control. |
TLS
Enable TLS 1.2 and 1.3 only, and let certbot manage certificates.
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;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=63072000" always;Add includeSubDomains to the HSTS header only when every subdomain serves HTTPS. Mozilla’s SSL Configuration Generator produces vetted cipher lists. Skip ssl_stapling with Let’s Encrypt certificates: they no longer carry an OCSP URL, so nginx logs "ssl_stapling" ignored. Issue certificates with certbot --nginx -d example.com.
Gzip
Compress text types only; images and archives are already compressed.
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 6; # 1 fastest to 9 smallest
gzip_vary on;
gzip_proxied any;
gzip_static on; # serve an existing file.css.gz; needs ngx_http_gzip_static_moduleGotchas
Avoid these; each causes 404s or lost headers.
| Gotcha | Fix |
|---|---|
proxy_pass http://b/; strips the location prefix; without the trailing slash the full URI passes through. | Choose the form that matches your upstream paths. |
add_header in a child block discards every add_header from the parent. | Repeat all headers in the child, or set them in one place with always. |
alias and root mix-ups return 404. | Use alias when the disk path differs from the URL prefix. |
| File order matters only among regex locations, not prefixes. | Order regexes deliberately. |
nginx -t does not check that upstreams are reachable. | Test with curl from the nginx host. |