Caddy as a reverse proxy for self-hosted services
I switched from Nginx to Caddy a couple of years ago and it remains one of the best infrastructure decisions I have made. I wrote about the switch itself in a previous post, but this one goes deeper into how I use Caddy day to day and the features that make it worth recommending.
Automatic HTTPS
This is the main selling point and it is not overstated. Caddy obtains and renews TLS certificates automatically using Let's Encrypt. You do not configure anything. Point a domain at your server, add it to the Caddyfile, and HTTPS just works.
No Certbot. No cron jobs. No manual renewal. No expired certificate emergencies at 2 AM.
The Caddyfile
Caddy's configuration is a file called Caddyfile. Here is a reverse proxy for three services:
nextcloud.example.com {
reverse_proxy localhost:8080
}
gitea.example.com {
reverse_proxy localhost:3000
}
monitoring.example.com {
reverse_proxy localhost:9090
}
That is the entire config. Each block is a domain name with a reverse proxy directive. Caddy handles HTTPS, HTTP to HTTPS redirects, and header management automatically.
Compare that to the equivalent Nginx config with SSL blocks, certificate paths, redirect rules, and proxy headers. The Caddyfile is dramatically simpler.
Running with Docker Compose
I run Caddy in Docker alongside my other services:
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:The caddy_data volume stores your TLS certificates. Keep it persistent and you will not hit rate limits when recreating the container.
Headers and security
Caddy sets sensible security headers by default. If you need more control:
app.example.com {
reverse_proxy localhost:3000
header {
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Basic authentication
Need to protect a service that does not have its own auth? Caddy has built-in basic auth:
admin.example.com {
basicauth {
admin $2a$14$hashed_password_here
}
reverse_proxy localhost:8080
}
Generate the hash with caddy hash-password.
When Nginx still makes sense
If you need advanced load balancing, complex routing rules, or you are already deep in the Nginx ecosystem with existing configs, switching has a cost. Caddy is also younger and has a smaller community, though it is growing fast.
For self-hosted services where you are adding and removing subdomains regularly, Caddy's simplicity wins. I migrated my entire setup in about 20 minutes and have not looked back.
Sources
Related posts
Tailscale: the easiest way to connect your devices
How Tailscale creates a mesh VPN between your devices without port forwarding, firewall rules, or a VPN server.
Setting up WireGuard for secure remote access
How to set up WireGuard VPN to securely access your home lab and self-hosted services from anywhere.
AdGuard Home: DNS-level ad blocking for your network
How AdGuard Home compares to Pi-hole for network-wide ad blocking, and why I switched to it for my homelab.
Enjoying the blog? Subscribe via RSS to get new posts in your reader.
Subscribe via RSS