Nginx — Reverse Proxy, Load Balancer, and SSL Configuration Guide
Advertisement
Introduction
Why This Matters
Nginx handles over 400 million websites and is the leading web server and reverse proxy. Its event-driven, non-blocking architecture allows it to handle tens of thousands of concurrent connections with minimal memory. Understanding Nginx configuration enables you to terminate SSL, load balance across backend services, cache responses, apply rate limiting, and serve static assets efficiently — all in a single, battle-tested process.
Installation and Basic Setup
# Install on Ubuntu/Debian
sudo apt update && sudo apt install nginx -y
# Install on CentOS/RHEL
sudo yum install epel-release && sudo yum install nginx -y
# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx
# Test configuration (always before reload)
sudo nginx -t
# Reload without dropping connections
sudo systemctl reload nginx
# Key file locations
# /etc/nginx/nginx.conf — main config
# /etc/nginx/conf.d/*.conf — site configs (included by main)
# /etc/nginx/sites-available/ — Debian-style site configs
# /var/log/nginx/access.log — access logs
# /var/log/nginx/error.log — error logsReverse Proxy Configuration
# /etc/nginx/conf.d/my-app.conf
upstream backend {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL configuration (Let's Encrypt)
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_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://backend;
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_cache_bypass $http_upgrade;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
}
# Serve static files directly (bypass Node.js)
location /static/ {
alias /var/www/my-app/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
}Load Balancing
upstream api_servers {
# Load balancing methods:
# round-robin (default) — distributes evenly
# least_conn — sends to server with fewest connections
# ip_hash — sticky sessions by client IP
least_conn;
server app1.internal:3000 weight=3;
server app2.internal:3000 weight=3;
server app3.internal:3000 weight=2;
# Health checking
server backup.internal:3000 backup;
# Connection limits
keepalive 32;
}
server {
listen 80;
location /api/ {
proxy_pass http://api_servers;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
proxy_next_upstream_tries 3;
}
}Rate Limiting
# Define rate limit zones in http block
http {
# Limit by IP: 10 requests per second, 1MB zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# Limit by API key header
limit_req_zone $http_x_api_key zone=api_key_limit:10m rate=100r/s;
server {
location /api/ {
# Allow burst of 20, queue up to 20 requests
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}Caching
http {
# Proxy cache configuration
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
server {
location /api/public/ {
proxy_cache my_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 5m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
}Gzip Compression
http {
gzip on;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
}Let's Encrypt with Certbot
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Obtain and install certificate
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renewal (cron or systemd timer)
sudo certbot renew --dry-run
# Certbot adds a systemd timer — verify it
systemctl status certbot.timerCommon Mistakes
- Not running
nginx -tbeforesystemctl reload nginx— invalid config causes Nginx to fail to load, dropping all traffic - Using
proxy_pass http://backend/;with trailing slash whenlocationhas no trailing slash — causes path rewriting bugs - Missing
proxy_set_header Host $host— backend receives wrong Host header, breaking virtual hosting - Not tuning
worker_processesandworker_connections— defaults are conservative; production needs tuning - Forgetting
add_headerwithalwaysparameter — headers only send on 2xx responses withoutalways
Best Practices
- Set
worker_processes autoto match CPU count andworker_connections 4096per worker - Enable
http2on SSL listeners for multiplexing and header compression - Use
proxy_cache_use_staleto serve stale cache during backend failures (graceful degradation) - Configure
access_logwith buffering (buffer=16k flush=5s) to reduce disk I/O - Use Nginx error pages to return JSON for API endpoints instead of HTML error pages
- Monitor Nginx with the
stub_statusmodule and integrate with Prometheus vianginx-prometheus-exporter
Key Takeaways
- Nginx uses an event-driven, non-blocking architecture — one worker process handles thousands of concurrent connections
upstreamblocks define backend pools; load balancing methods include round-robin, least connections, and IP hash- Always run
nginx -tto validate configuration before reloading — invalid config prevents reload without downtime - Rate limiting with
limit_req_zoneprotects backends from traffic spikes and brute-force attacks - Proxy caching with
proxy_cache_pathreduces backend load for idempotent GET responses - SSL termination at Nginx offloads TLS handshake CPU from application servers
- Security headers (
HSTS,X-Content-Type-Options,X-Frame-Options) should be added at the proxy layer, not the application - Let's Encrypt with Certbot provides free, auto-renewing TLS certificates with Nginx integration
Advertisement