Quick reference tables
Core directives
| Directive | What it does |
|---|
worker_processes auto; | Set worker processes to match CPU cores |
worker_connections 1024; | Max connections per worker |
keepalive_timeout 65; | Keep-alive connection timeout in seconds |
server_tokens off; | Hide Nginx version in headers and error pages |
include /etc/nginx/conf.d/*.conf; | Load additional config files |
include /etc/nginx/sites-enabled/*; | Load site configs (Debian/Ubuntu layout) |
Server block structure
| Directive | What it does |
|---|
listen 80; | Listen on port 80 (IPv4) |
listen 80 default_server; | Default server for unmatched requests |
listen [::]:80; | Listen on port 80 (IPv6) |
server_name example.com www.example.com; | Match these hostnames |
server_name _; | Catch-all server block |
root /var/www/html; | Document root directory |
index index.html index.php; | Default index files |
charset utf-8; | Set response charset |
Location matching (priority order)
| Syntax | Match type | Priority |
|---|
location = /exact | Exact match | Highest |
location ^~ /prefix | Prefix match (stops regex search) | High |
location ~ \.php$ | Case-sensitive regex | Medium |
location ~* \.jpg$ | Case-insensitive regex | Medium |
location /prefix | Prefix match (continues to check regex) | Low |
location / | Catch-all | Lowest |
Proxy pass (reverse proxy)
| Directive | What it does |
|---|
proxy_pass http://localhost:3000; | Forward to upstream server |
proxy_pass http://localhost:3000/; | Trailing slash strips the location prefix |
proxy_http_version 1.1; | Required for WebSocket and keep-alive |
proxy_set_header Host $host; | Pass original Host header |
proxy_set_header X-Real-IP $remote_addr; | Pass client IP |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | Append to X-Forwarded-For chain |
proxy_set_header X-Forwarded-Proto $scheme; | Pass http or https |
proxy_set_header Upgrade $http_upgrade; | Required for WebSocket |
proxy_set_header Connection "upgrade"; | Required for WebSocket |
proxy_buffering off; | Disable buffering (needed for SSE/streaming) |
proxy_read_timeout 60s; | Upstream response timeout |
proxy_connect_timeout 10s; | Time to establish upstream connection |
SSL / TLS
| Directive | What it does |
|---|
listen 443 ssl; | Listen on HTTPS |
ssl_certificate /etc/ssl/cert.pem; | Path to certificate (full chain) |
ssl_certificate_key /etc/ssl/key.pem; | Path to private key |
ssl_protocols TLSv1.2 TLSv1.3; | Only allow TLS 1.2 and 1.3 |
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:...; | Allowed cipher suites |
ssl_prefer_server_ciphers on; | Prefer server’s cipher order |
ssl_session_cache shared:SSL:10m; | SSL session cache (10MB) |
ssl_session_timeout 1d; | SSL session reuse window |
ssl_stapling on; | Enable OCSP stapling |
ssl_stapling_verify on; | Verify OCSP responses |
Redirects
| Directive | What it does |
|---|
return 301 https://$host$request_uri; | Permanent redirect (HTTP → HTTPS) |
return 302 /new-path; | Temporary redirect |
rewrite ^/old$ /new permanent; | Regex-based permanent redirect |
rewrite ^/old(.*)$ /new$1 permanent; | Redirect with path preservation |
| Directive | What it does |
|---|
add_header X-Frame-Options "DENY"; | Prevent clickjacking |
add_header X-Content-Type-Options "nosniff"; | Prevent MIME sniffing |
add_header X-XSS-Protection "1; mode=block"; | XSS protection (legacy) |
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; | HSTS |
add_header Referrer-Policy "strict-origin-when-cross-origin"; | Referrer policy |
add_header Content-Security-Policy "default-src 'self'"; | CSP |
add_header always | Apply header even on error responses |
Static file serving and caching
| Directive | What it does |
|---|
expires 1y; | Set Cache-Control and Expires headers |
expires -1; | Disable caching for this location |
add_header Cache-Control "public, immutable"; | Immutable assets (hashed filenames) |
etag on; | Enable ETag generation |
if_modified_since exact; | Exact Last-Modified comparison |
open_file_cache max=1000 inactive=20s; | Cache open file descriptors |
gzip on; | Enable gzip compression |
gzip_types text/plain text/css application/json application/javascript; | MIME types to compress |
gzip_min_length 1000; | Don’t compress files smaller than 1KB |
gzip_comp_level 6; | Compression level 1–9 (6 is a good balance) |
Rate limiting
| Directive | What it does |
|---|
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; | Define zone (in http block) |
limit_req zone=api burst=20 nodelay; | Apply limit in location block |
limit_req_status 429; | Return 429 Too Many Requests |
limit_conn_zone $binary_remote_addr zone=conn:10m; | Connection limit zone |
limit_conn conn 10; | Max concurrent connections per IP |
Upstream (load balancing)
| Directive | What it does |
|---|
upstream backend { ... } | Define upstream group |
server 127.0.0.1:3000; | Add server to upstream |
server 127.0.0.1:3001 weight=2; | Weighted load balancing |
server 127.0.0.1:3002 backup; | Failover server |
least_conn; | Route to server with fewest active connections |
ip_hash; | Sticky sessions by client IP |
keepalive 32; | Keep persistent connections to upstream |
Common CLI commands
| Command | What it does |
|---|
nginx -t | Test config syntax (always run before reload) |
nginx -T | Test config and dump full compiled config |
nginx -s reload | Reload config without dropping connections |
nginx -s stop | Fast shutdown |
nginx -s quit | Graceful shutdown |
systemctl reload nginx | Reload via systemd |
systemctl restart nginx | Full restart via systemd |
nginx -v | Show version |
nginx -V | Show version and compile options |
Detailed sections
Production-ready HTTPS server block
This is a complete, secure config for a Node.js/Python app behind Nginx. Replace example.com and the upstream port with your own values.
# Redirect all HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
# SSL
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;
ssl_stapling on;
ssl_stapling_verify on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
server_tokens off;
# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript application/xml;
gzip_min_length 1000;
# Proxy to Node.js app
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 60s;
}
# Static assets with long cache
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Block dotfiles
location ~ /\. {
deny all;
}
}
Location block matching — the order matters
Nginx picks the most specific matching location. This is one of the most common sources of config bugs.
server {
# 1. Exact match — highest priority
location = /health {
return 200 "OK";
}
# 2. Prefix match with regex-stop — second priority
# Use ^~ when you want a prefix match that beats any regex
location ^~ /static/ {
root /var/www;
expires 1y;
}
# 3. Regex matches — evaluated in order, first match wins
location ~* \.(php|asp|aspx)$ {
return 403; # Block server-side scripts in wrong places
}
# 4. Catch-all prefix — lowest priority
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Rate limiting — protect your API
Define the zone in the http {} block, apply it in server {} or location {}.
http {
# Zone: 10MB of state (stores ~160k unique IPs)
# Rate: max 10 requests per second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_status 429;
server {
location /api/ {
# burst=20: allow up to 20 queued requests above the rate
# nodelay: serve burst immediately instead of spacing them out
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}
}
WebSocket reverse proxy
WebSockets require two specific headers. Without them, the connection upgrade fails.
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # Keep WS connections open longer
}
Certbot / Let’s Encrypt integration
Get a free certificate and auto-configure Nginx in one command. Requires the certbot and python3-certbot-nginx packages.
# Install certbot (Debian/Ubuntu)
sudo apt install certbot python3-certbot-nginx
# Get and install certificate
sudo certbot --nginx -d example.com -d www.example.com
# Test auto-renewal
sudo certbot renew --dry-run
Certbot edits your config in place, adding the SSL directives. After it runs, always check nginx -t before assuming the config is valid.
Related: Linux Bash Cheat Sheet | SSH & GPG Cheat Sheet | How to Set Up a Reverse Proxy with Nginx
Next_Recommended_Node
jq Cheat Sheet: Filter, Transform, and Query JSON from the Terminal
Complete jq reference — basic filters, pipes, array/object operations, conditionals, string interpolation, select, map, reduce, and real DevOps workflows.