A reverse proxy sits in front of your application servers, handling incoming requests and distributing them to backend services. It is essential for TLS termination, load balancing, caching, and security. This guide covers two of the most popular options: Nginx and Caddy.
Why Use a Reverse Proxy
Nginx Reverse Proxy
Nginx is the industry standard for reverse proxying. It is mature, highly performant, and extremely configurable.
Basic Reverse Proxy Configuration
server {
listen 80;
server_name app.example.com;
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;
}
}
The `proxy_pass` directive sends requests to the backend. Always forward the original host and client IP headers so your application has accurate client information.
WebSocket Support
location /ws/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s;
}
The `Upgrade` and `Connection` headers are required for WebSocket connections. Set `proxy_read_timeout` to a long duration since WebSocket connections remain open.
Load Balancing
Distribute traffic across multiple backends:
upstream app_cluster {
least_conn;
server 10.0.0.1:3000 weight=3;
server 10.0.0.2:3000;
server 10.0.0.3:3000 backup;
}
server {
location / {
proxy_pass http://app_cluster;
}
}
Load balancing methods: `round-robin` (default), `least_conn` (fewest active connections), `ip_hash` (session persistence). Assign higher `weight` to more powerful servers.
Caching
Cache responses from the backend:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=appcache:10m max_size=1g;
server {
location / {
proxy_cache appcache;
proxy_cache_valid 200 30m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
add_header X-Cache-Status $upstream_cache_status;
}
}
The `$upstream_cache_status` header helps debug caching (HIT, MISS, STALE, etc.). `proxy_cache_use_stale` serves stale content when the backend is down.
Caddy Reverse Proxy
Caddy is a modern web server with automatic HTTPS, simpler configuration, and Go-based performance. It is ideal for teams that want a zero-fuss reverse proxy.
Basic Reverse Proxy
# Caddyfile
app.example.com {
reverse_proxy localhost:3000
}
That is the entire configuration. Caddy automatically obtains and renews Let's Encrypt TLS certificates.
Multiple Backends with Load Balancing
app.example.com {
reverse_proxy 10.0.0.1:3000 10.0.0.2:3000 10.0.0.3:3000 {
lb_policy least_conn
health_uri /health
health_interval 30s
}
}
Caddy supports multiple load balancing policies: `random`, `least_conn`, `round_robin`, `first`, `ip_hash`.
Path-Based Routing
Route different paths to different services:
api.example.com {
reverse_proxy /api/* localhost:3000
reverse_proxy /auth/* localhost:3001
reverse_proxy localhost:3002 # default
}
Request Manipulation
app.example.com {
reverse_proxy localhost:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
# Add security headers
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
}
Caddy's `header` directive works for both request and response headers.
Nginx vs Caddy: Comparison
| Feature | Nginx | Caddy |
|---------|-------|-------|
| Configuration | Complex, powerful | Simple, opinionated |
| TLS | Manual cert management | Automatic Let's Encrypt |
| Performance | Excellent | Very good |
| Ecosystem | Vast (modules, guides) | Growing but smaller |
| Docker support | Official image | Official image |
| Learning curve | Steep | Gentle |
| Dynamic configuration | Limited | REST API available |
| HTTP/3 | Supported | Supported |
Security Headers
Regardless of which reverse proxy you choose, add these security headers:
For Nginx:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self';" always;
For Caddy:
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
Rate Limiting
Nginx:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
Caddy:
app.example.com {
rate_limit {
zone api {
key {remote_host}
events 10
window 1s
}
}
reverse_proxy localhost:3000
}
Health Checks
Nginx health checks require the Plus version or are handled externally (e.g., via Docker health checks). Caddy includes built-in active health checks that mark unhealthy backends as down and stop routing traffic to them.
Summary
Nginx and Caddy are both excellent reverse proxies. Nginx offers unmatched flexibility and performance for complex deployments. Caddy provides automatic TLS and simpler configuration, making it ideal for smaller teams or simpler setups. For TLS-heavy deployments, Caddy's automatic certificates save significant operational overhead. For high-traffic scenarios requiring fine-grained control, Nginx remains the standard. Both can route traffic, terminate TLS, add security headers, and cache responses -- choose based on your team's expertise and operational complexity tolerance.