Reverse Proxy: Nginx and Caddy Configuration

A reverse proxy sits between clients and your backend servers, forwarding requests and responses while adding critical functionality. Unlike forward proxies that serve clients, reverse proxies serve...

Key Insights

  • Reverse proxies sit between clients and backend servers, handling SSL termination, load balancing, and security—Nginx offers maximum control and performance while Caddy prioritizes simplicity with automatic HTTPS
  • Nginx requires manual SSL certificate management and more verbose configuration, but provides fine-grained control over caching, rate limiting, and request handling that scales to millions of requests
  • Caddy’s automatic HTTPS via Let’s Encrypt and minimal configuration make it ideal for smaller deployments, but Nginx remains the industry standard for high-traffic production environments requiring extensive customization

Introduction to Reverse Proxies

A reverse proxy sits between clients and your backend servers, forwarding requests and responses while adding critical functionality. Unlike forward proxies that serve clients, reverse proxies serve servers—they’re the gateway to your application infrastructure.

Reverse proxies handle several essential tasks: SSL/TLS termination (decrypting HTTPS traffic so backends deal with plain HTTP), load balancing across multiple servers, caching static content, and providing a security boundary. They also normalize client requests, add security headers, and can mitigate DDoS attacks through rate limiting.

Nginx and Caddy represent two philosophies in reverse proxy design. Nginx, created in 2004, is the battle-tested industry standard powering over 30% of websites. It offers maximum configurability and performance but requires manual setup for features like SSL. Caddy, released in 2015, prioritizes developer experience with automatic HTTPS and simpler configuration syntax. Choose Nginx when you need proven scalability and fine-grained control; choose Caddy when you want to ship faster with less operational overhead.

Basic Nginx Reverse Proxy Configuration

Install Nginx on Ubuntu/Debian:

sudo apt update
sudo apt install nginx

The core of Nginx reverse proxying is the proxy_pass directive. Here’s a basic configuration for proxying to a Node.js application running on localhost:3000:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        
        # Forward original client information
        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;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Save this to /etc/nginx/sites-available/example.com, create a symlink to sites-enabled, and reload:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

The proxy_set_header directives are critical—they preserve the original client’s IP address and protocol. Without these, your backend only sees requests from 127.0.0.1 and can’t distinguish between HTTP and HTTPS.

Basic Caddy Reverse Proxy Configuration

Install Caddy:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

The equivalent Caddy configuration is dramatically simpler:

example.com {
    reverse_proxy localhost:3000
}

That’s it. This two-line Caddyfile automatically:

  • Obtains and renews SSL certificates from Let’s Encrypt
  • Redirects HTTP to HTTPS
  • Sets proper forwarding headers
  • Enables HTTP/2 and HTTP/3

Save this to /etc/caddy/Caddyfile and reload:

sudo systemctl reload caddy

Caddy handles the complexity behind the scenes. It automatically sets X-Forwarded-For, X-Forwarded-Proto, and X-Real-IP headers, manages certificate renewal, and configures secure TLS settings.

Advanced Configuration Patterns

Load Balancing

Nginx uses upstream blocks to define backend pools:

upstream backend_pool {
    least_conn;  # or ip_hash, round_robin (default)
    server 10.0.1.10:3000 weight=3;
    server 10.0.1.11:3000;
    server 10.0.1.12:3000 backup;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Caddy’s approach is equally straightforward:

example.com {
    reverse_proxy localhost:3000 localhost:3001 localhost:3002 {
        lb_policy least_conn
        health_uri /health
        health_interval 10s
    }
}

Path-Based Routing

Route different paths to different backends—common for separating API servers from static content:

server {
    listen 80;
    server_name example.com;

    location /api {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
    }

    location /static {
        proxy_pass http://localhost:8080;
        proxy_cache_valid 200 1h;
    }

    location / {
        proxy_pass http://localhost:4000;
    }
}

Caddy equivalent:

example.com {
    handle /api* {
        reverse_proxy localhost:3000
    }

    handle /static* {
        reverse_proxy localhost:8080
    }

    handle {
        reverse_proxy localhost:4000
    }
}

WebSocket Support

WebSockets require connection upgrades. Nginx:

location /ws {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

Caddy handles WebSockets automatically—no special configuration needed.

SSL/TLS Configuration

Nginx requires manual certificate management:

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
    
    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers off;
    
    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    
    location / {
        proxy_pass http://localhost:3000;
    }
}

# HTTP to HTTPS redirect
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

Caddy’s automatic HTTPS:

example.com {
    reverse_proxy localhost:3000
}

For custom certificates in Caddy:

example.com {
    tls /path/to/cert.pem /path/to/key.pem
    reverse_proxy localhost:3000
}

Performance and Security Hardening

Rate Limiting

Nginx rate limiting prevents abuse:

http {
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    
    server {
        location /api {
            limit_req zone=api_limit burst=20 nodelay;
            proxy_pass http://localhost:3000;
        }
    }
}

Caddy uses the rate_limit directive:

example.com {
    rate_limit {
        zone api_zone {
            key {remote_host}
            events 10
            window 1s
        }
    }
    
    reverse_proxy localhost:3000
}

Security Headers

Nginx security headers:

server {
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    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 Content-Security-Policy "default-src 'self'" always;
    
    location / {
        proxy_pass http://localhost:3000;
    }
}

Caddy security headers:

example.com {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        X-XSS-Protection "1; mode=block"
        Content-Security-Policy "default-src 'self'"
    }
    
    reverse_proxy localhost:3000
}

Choosing Between Nginx and Caddy

Use Nginx when:

  • You need maximum performance (handles 10k+ requests/second easily)
  • You require extensive customization and fine-grained control
  • You’re running high-traffic production systems
  • You need advanced features like complex caching rules, sophisticated load balancing algorithms, or Lua scripting
  • Your team has existing Nginx expertise

Use Caddy when:

  • You want automatic HTTPS without manual certificate management
  • You prioritize development speed over configuration flexibility
  • You’re building internal tools, side projects, or small-to-medium traffic sites
  • You prefer readable configuration over maximum performance
  • You want built-in HTTP/3 support without compilation flags

Quick Reference:

Feature Nginx Caddy
Automatic HTTPS Manual (certbot) Built-in
Configuration Complexity High Low
Performance Excellent Very Good
Memory Usage Lower Higher
HTTP/3 Support Requires compilation Built-in
Learning Curve Steep Gentle
Market Share ~33% ~1%

Both tools excel at reverse proxying. Nginx gives you power and control; Caddy gives you simplicity and speed. For most production workloads, Nginx’s proven track record and performance characteristics make it the safer choice. For rapid development and smaller deployments where automatic HTTPS is valuable, Caddy eliminates operational overhead. Choose based on your team’s expertise, traffic requirements, and tolerance for configuration complexity.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.