A reverse proxy sits between the internet and your application servers. It handles SSL termination, load balancing, caching, rate limiting, and request routing — so your app doesn't have to.
Why Use a Reverse Proxy
SSL termination: Handle HTTPS at the proxy, app speaks plain HTTP internally
Load balancing: Distribute requests across multiple app instances
Static file serving: Serve assets directly without hitting your app
Caching: Cache responses to reduce backend load
Security: Hide backend topology, add rate limiting, filter bad requests
Compression: Gzip/Brotli responses automatically
Nginx — Full Production Configuration
# /etc/nginx/sites-available/myapp.conf
upstream app_backend {
# Load balancing across multiple app instances
server 127.0.0.1:3000 weight=3;
server 127.0.0.1:3001 weight=2;
server 127.0.0.1:3002 backup; # Only used if others are down
# Health checks (Nginx Plus) or use passive checks
keepalive 32; # Persistent connections to backend
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL Configuration
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_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
# Static files — served directly by Nginx (fast)
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API — proxy to backend
location /api/ {
proxy_pass http://app_backend;
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;
proxy_set_header Connection "";
# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Rate limiting
limit_req zone=api burst=20 nodelay;
}
# WebSocket support
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400; # Keep WebSocket alive
}
}
# Rate limiting zone (defined in nginx.conf http block)
# limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
Caddy vs Nginx: Caddy automatically obtains and renews SSL certificates with zero configuration. For new projects, Caddy is often the better choice — less config, automatic HTTPS, modern defaults. Nginx is better when you need maximum performance tuning or have complex routing requirements.