nginx -t && nginx -s reloadif for routing inside location blocks — it causes surprising behavior. Use map or separate server blocks.nginx -t before nginx -s reload.server_tokens off — never expose version.if inside location blocks for routing.worker_connections * worker_processes must not exceed ulimit -n.proxy_buffering on (default).| Directive | Context | Default | Purpose |
|---|---|---|---|
worker_processes | main | auto | Set to CPU core count |
worker_connections | events | 512 | Max connections per worker |
server_tokens | http | on | Set to off to hide version |
client_max_body_size | http/server | 1m | Max upload size |
proxy_pass | location | — | Backend URL |
proxy_set_header Host | location | $proxy_host | Set to $host |
ssl_protocols | http/server | TLSv1.2 TLSv1.3 | TLS versions |
ssl_certificate | server | — | Path to fullchain.pem |
ssl_session_cache | http | none | Set to shared:SSL:10m |
gzip | http | off | Enable compression |
limit_req_zone | http | — | Rate limiting |
upstream | http | — | Load balancer pool |
START
├── Serving static files only?
│ ├── YES → Static file server config
│ └── NO ↓
├── Proxying to one backend?
│ ├── YES → Single reverse proxy
│ └── NO ↓
├── Multiple backends?
│ ├── YES → Upstream load balancer
│ └── NO ↓
├── Need SSL termination?
│ ├── YES → SSL block with Let's Encrypt
│ └── NO ↓
└── DEFAULT → Reverse proxy + SSL + security headers
Install on your platform. [src6]
sudo apt update && sudo apt install -y nginx
Verify: nginx -v → nginx version shown
Route to backend application. [src1]
server {
listen 80;
server_name example.com;
server_tokens off;
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;
}
}
Verify: sudo nginx -t && sudo nginx -s reload
Enable HTTPS. [src2]
sudo certbot --nginx -d example.com
Verify: curl -I https://example.com → HTTP/2 200
Protect against abuse. [src5]
# In http block
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# In location
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
Verify: send burst of requests, check for 429 responses
# Input: Static file requests
# Output: Files with cache headers
server {
listen 80;
server_name static.example.com;
root /var/www/static;
server_tokens off;
location ~* \.(css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; }
location ~* \.(png|jpg|svg|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; }
location ~ /\. { deny all; return 404; }
}
# Input: Production nginx.conf
# Output: Hardened reverse proxy
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
events { worker_connections 4096; multi_accept on; }
http {
include /etc/nginx/mime.types;
server_tokens off;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
include /etc/nginx/conf.d/*.conf;
}
# ❌ BAD — "if is evil" in nginx
location / {
if ($request_uri ~* "/api") { proxy_pass http://api; }
}
# ✅ GOOD — explicit matching
location /api/ { proxy_pass http://api_backend; }
location / { proxy_pass http://web_backend; }
# ❌ BAD — backend sees proxy_host
location / { proxy_pass http://127.0.0.1:3000; }
# ✅ GOOD — original context preserved
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
# ❌ BAD — version exposed, no protection
server { listen 80; server_name example.com; }
# ✅ GOOD — all protections enabled
server {
listen 443 ssl http2;
server_tokens off;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=63072000" always;
}
nginx -t && nginx -s reload. [src3]10m or higher. [src4]65535. [src4]ssl_protocols TLSv1.2 TLSv1.3;. [src5]allow 127.0.0.1; deny all;. [src7]# Test config syntax
sudo nginx -t
# Reload (zero-downtime)
sudo nginx -s reload
# Show compiled modules
nginx -V
# View access logs live
tail -f /var/log/nginx/access.log
# Check open connections
ss -tlnp | grep nginx
# Test SSL
openssl s_client -connect example.com:443 -tls1_2
# Check cert expiry
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| nginx 1.27.x | Mainline | HTTP/3 improvements | Latest features |
| nginx 1.26.x | Stable | None | Recommended for production |
| nginx 1.24.x | Previous stable | — | Widely deployed |
| OpenSSL 3.x | Current | Deprecated algorithms | Required for TLS 1.3 |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Reverse proxy for any backend | Need automatic HTTPS | Caddy |
| High-performance static serving | Simple dev proxy | webpack-dev-server, Vite |
| Load balancing | Service mesh | Envoy, Istio |
| SSL termination | Edge compute | Cloudflare Workers |
| Rate limiting | Full WAF | ModSecurity, Cloudflare WAF |
if directive is NOT a general-purpose conditional — only reliable for return and rewrite in server context.proxy_pass URI handling changes based on trailing slash presence.nginx -s reload required.--with-http_v3_module.