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.

BlockHolds
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.

OrderFormRule
1location = /pathExact match wins immediately.
2location ^~ /pathIf it is the longest matching prefix, skip regex checks.
3location ~ re, ~* reRegexes (case-sensitive, case-insensitive) in file order; first match wins.
4location /pathLongest 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; }
DirectiveUse
return 301 /new;Redirect without regex.
try_files $uri =404;Last argument is the fallback: a status or a named location.
root vs aliasroot 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_module

Gotchas

Avoid these; each causes 404s or lost headers.

GotchaFix
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.