Production Nginx Configuration Structure
Beyond a simple reverse proxy, this guide covers patterns for using Nginx as a load balancer, cache server, and a front-line layer in front of a WAF.
1. Upstream Load-Balancing Strategies
Basic Configuration (Round Robin)
Nginx
http {
upstream app_servers {
least_conn; # Distribute by fewest connections (recommended over Round Robin)
server 10.0.1.10:8080 weight=3; # Weight: 30% of traffic
server 10.0.1.11:8080 weight=2;
server 10.0.1.12:8080 weight=1;
server 10.0.1.13:8080 backup; # Used when all others are down
keepalive 32; # upstream keepalive connection pool
keepalive_timeout 60s;
}
server {
listen 443 ssl http2;
server_name api.example.com;
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1;
proxy_set_header Connection ""; # preserve keepalive
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;
}
}
}Session Persistence (Sticky Sessions)
Nginx
upstream app_servers {
ip_hash; # Same client IP always goes to the same server
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}Health Checks (Manual Implementation Without Nginx Plus)
Nginx
upstream app_servers {
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
}
# max_fails: temporarily exclude the server after 3 failures within 30 seconds2. proxy_cache — Content Caching
Nginx
http {
# Define the cache store (top-level http block in nginx.conf)
proxy_cache_path /var/cache/nginx
levels=1:2 # directory hierarchy
keys_zone=app_cache:50m # 50MB in-memory key store
max_size=10g # maximum disk usage
inactive=60m # delete if not accessed for 60 minutes
use_temp_path=off;
server {
location / {
proxy_pass http://app_servers;
proxy_cache app_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m; # cache 200/302 responses for 10 minutes
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_lock on; # prevent duplicate requests for the same key (thundering herd)
proxy_cache_lock_timeout 5s;
# expose cache status header (for debugging)
add_header X-Cache-Status $upstream_cache_status;
}
# Cache bypass: under specific conditions
location /api/user {
proxy_pass http://app_servers;
proxy_cache_bypass $cookie_session; # skip cache if a session cookie is present
proxy_no_cache $cookie_session;
}
# Cache purge (internal only)
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
proxy_cache_purge app_cache "$scheme$request_method$host$1";
}
}
}Bash
# Check cache status
curl -I https://example.com/static/logo.png | grep X-Cache-Status
# HIT / MISS / BYPASS / UPDATING
# Check cache directory size
du -sh /var/cache/nginx
# Flush the entire cache
sudo rm -rf /var/cache/nginx/*
sudo nginx -s reload3. Rate Limiting — DDoS / Brute-Force Defense
Nginx
http {
# Define request rate-limit zones
limit_req_zone $binary_remote_addr zone=api_limit:20m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=3r/m;
limit_req_zone $http_x_api_key zone=key_limit:10m rate=100r/s;
# Connection-count limit zone
limit_conn_zone $binary_remote_addr zone=conn_limit:20m;
server {
# API endpoint: 10 req/s, burst of 20 allowed
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 20;
limit_req_status 429;
proxy_pass http://app_servers;
}
# Login: 3 per minute; queue on excess (no nodelay)
location /auth/login {
limit_req zone=login_limit burst=5;
limit_req_status 429;
proxy_pass http://app_servers;
}
# Customize the rate-limit exceeded response
error_page 429 /rate_limit.json;
location = /rate_limit.json {
internal;
default_type application/json;
return 429 '{"error":"Too Many Requests","retry_after":60}';
}
}
}4. Security Headers and SSL Hardening
Nginx
http {
# TLS settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
# HSTS (Strict-Transport-Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Generate and apply DH parameters
ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -out /etc/nginx/dhparam.pem 4096
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
server {
# Apply security headers in bulk
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; img-src 'self' data: cdn.example.com; style-src 'self' 'unsafe-inline';" always;
# Hide server information
server_tokens off;
# HTTP → HTTPS redirect
if ($scheme = http) {
return 301 https://$host$request_uri;
}
}
}5. Performance Tuning — Worker and Connection Optimization
Nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # Auto-set to match CPU core count
worker_rlimit_nofile 65535; # file descriptor limit
events {
worker_connections 4096; # max connections per worker
use epoll; # Linux-optimized event method
multi_accept on; # accept multiple connections at once
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss;
gzip_min_length 1024;
# Buffer tuning
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}Bash
# Validate configuration
sudo nginx -t
# Zero-downtime config reload
sudo nginx -s reload
# Real-time connection stats
sudo nginx -V 2>&1 | grep with-http_stub_status
# Check the status page
curl http://localhost/nginx_statusEditorial note
AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us
Topic-related official docs (auto-linked · not per-claim sources)NGINX documentation ↗OWASP documentation ↗
Questions & answers
Ask a question about this guide. We'll review and reply.