DEV Community

Cover image for **_**_**_ **_Using Caddy as a Reverse Proxy with Automatic HTTP_**S_**_**_**
MaxTezza
MaxTezza

Posted on

**_**_**_ **_Using Caddy as a Reverse Proxy with Automatic HTTP_**S_**_**_**

description: Simplify your home lab or VPS setup with Caddy's zero-config automatic HTTPS and dead-simple reverse proxy configuration
tags: homelab, devops, webdev, tutorial

If you're running multiple services on your home lab or VPS, manually managing HTTPS certificates and reverse proxy configurations can become tedious fast. Caddy is a web server that automatically obtains and renews Let's Encrypt certificates with zero configuration, making it perfect for exposing your services securely.

Why Caddy Over nginx or Apache

I switched to Caddy after spending years with nginx, and the difference is striking. With nginx, you need Certbot, cron jobs for renewal, and careful configuration management. With Caddy, you literally just specify the domain name and it handles everything. The configuration syntax is also dramatically simpler—what takes 50 lines in nginx takes 5 in Caddy.

Installing Caddy

On Ubuntu/Debian:

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
Enter fullscreen mode Exit fullscreen mode

Caddy will automatically start as a systemd service.

Basic Reverse Proxy Configuration

Create or edit /etc/caddy/Caddyfile:

# Simple reverse proxy to a local service
myapp.example.com {
    reverse_proxy localhost:3000
}

# Proxy with custom headers
api.example.com {
    reverse_proxy localhost:8080 {
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Proto {scheme}
    }
}

# Multiple services on different paths
example.com {
    reverse_proxy /api/* localhost:8080
    reverse_proxy /web/* localhost:3000
    reverse_proxy /* localhost:8081
}
Enter fullscreen mode Exit fullscreen mode

That's it. No SSL configuration needed. Caddy automatically:

  • Obtains Let's Encrypt certificates
  • Redirects HTTP to HTTPS
  • Renews certificates before expiry
  • Serves HTTPS on port 443

Real-World Example: Home Lab Setup

Here's my actual Caddyfile for my home lab services:

# Jellyfin media server
jellyfin.mydomain.com {
    reverse_proxy localhost:8096
}

# Home Assistant
home.mydomain.com {
    reverse_proxy localhost:8123
}

# Grafana monitoring
grafana.mydomain.com {
    reverse_proxy localhost:3001
}

# Portainer container management
portainer.mydomain.com {
    reverse_proxy localhost:9000 {
        transport http {
            tls_insecure_skip_verify
        }
    }
}

# Catch-all for root domain
mydomain.com {
    respond "Home Lab Services" 200
}
Enter fullscreen mode Exit fullscreen mode

After editing the Caddyfile, reload Caddy:

sudo systemctl reload caddy
Enter fullscreen mode Exit fullscreen mode

Watch the logs to see certificates being obtained:

sudo journalctl -u caddy -f
Enter fullscreen mode Exit fullscreen mode

You'll see Caddy automatically reaching out to Let's Encrypt and obtaining certificates for each domain.

Advanced Configuration: Docker Container Backend

If your services run in Docker, you can proxy to them by container name when Caddy is on the same Docker network:

version: '3.8'

services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - webproxy

  myapp:
    image: myapp:latest
    networks:
      - webproxy

networks:
  webproxy:
    driver: bridge

volumes:
  caddy_data:
  caddy_config:
Enter fullscreen mode Exit fullscreen mode

Then in your Caddyfile:

myapp.example.com {
    reverse_proxy myapp:3000
}
Enter fullscreen mode Exit fullscreen mode

Handling Websockets

Many modern apps use WebSockets. Caddy handles them automatically with no extra configuration:

chat.example.com {
    reverse_proxy localhost:3000
}
Enter fullscreen mode Exit fullscreen mode

That's it. Unlike nginx where you need specific upgrade headers, Caddy detects and handles WebSocket connections automatically.

Load Balancing Multiple Backends

If you're running multiple instances of a service:

api.example.com {
    reverse_proxy localhost:8001 localhost:8002 localhost:8003 {
        lb_policy round_robin
        health_uri /health
        health_interval 10s
    }
}
Enter fullscreen mode Exit fullscreen mode

Caddy will distribute requests across backends and automatically remove unhealthy ones from rotation.

Security Headers

Add security headers easily:

example.com {
    header {
        Strict-Transport-Security "max-age=31536000;"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "no-referrer-when-downgrade"
    }
    reverse_proxy localhost:3000
}
Enter fullscreen mode Exit fullscreen mode

Authentication Layer

Add basic auth in front of any service:

admin.example.com {
    basicauth {
        admin $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4wHaNzfF29C
    }
    reverse_proxy localhost:9000
}
Enter fullscreen mode Exit fullscreen mode

Generate the hash with:

caddy hash-password
Enter fullscreen mode Exit fullscreen mode

Common Gotchas

Port 80/443 Already in Use: If you have Apache or nginx running, stop them first. Only one process can listen on these ports.

DNS Not Pointing to Server: Caddy needs to complete the ACME challenge. Your domain must resolve to your server's public IP before Caddy can obtain certificates.

Firewall Blocking: Ensure ports 80 and 443 are open in your firewall:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Enter fullscreen mode Exit fullscreen mode

IPv6 Issues: If you see IPv6 errors but only use IPv4:

{
    auto_https disable_redirects
}

myapp.example.com {
    bind 0.0.0.0
    reverse_proxy localhost:3000
}
Enter fullscreen mode Exit fullscreen mode

Performance Considerations

Caddy is written in Go and is very efficient, but for high-traffic scenarios, tune these settings:

{
    servers {
        timeouts {
            read_body 10s
            read_header 5s
            write 30s
            idle 120s
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

What This Replaced

Before Caddy, my nginx setup required:

  • Initial nginx configuration (30+ lines per service)
  • Certbot installation and configuration
  • Systemd timer or cron job for renewal
  • Manual renewal testing
  • Separate configuration for HTTP->HTTPS redirects
  • Regular maintenance to ensure renewals work

With Caddy, I have a 3-line config per service and zero maintenance. I've been running it for two years and have never thought about certificates once.

Getting Started

Start small. Pick one service you're currently accessing via HTTP or with a self-signed certificate. Add a 3-line Caddy config for it, point the DNS, and watch it work. Once you see how trivial it is, you'll migrate everything else quickly.

The combination of automatic HTTPS and simple configuration makes Caddy the obvious choice for home labs and small-scale deployments. It's one of those tools that makes you wonder how you ever did it the old way.

What's your go-to reverse proxy? Have you tried Caddy? Drop a comment below!

Top comments (0)