Caddy
Fast, multi-platform web server with automatic HTTPS
Caddy is a powerful, extensible web server and reverse proxy written in Go that provides automatic HTTPS out of the box. Its simple configuration makes it popular for self-hosting.
Caddy setup guides & articles
Hands-on coverage of Caddy from the blog.
Key features
- Automatic HTTPS by default
- Simple Caddyfile config
- Single binary
- Plugin ecosystem
Quick deploy
A starting point for self-hosting Caddy - check the official docs for the full set of options.
- Persist
/etc/caddy/Caddyfile/data
Docker Compose
services:
caddy:
image: caddy:latest
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data
- ./config:/config
restart: unless-stoppedWatch out for
- Persist /data or every container recreate re-issues certificates and burns rate limits
- The 443/udp mapping is what enables HTTP/3 - keep it
Pros & cons
Strengths
- Zero-config TLS
- Easy to learn
Trade-offs
- Fewer tuning knobs than Nginx
Caddy replaces
Last reviewed Aug 23, 2026 · 641 words
Caddy's pitch is that a reverse proxy with automatic HTTPS should be a five-line text file, and it delivers: point DNS at your box, write a Caddyfile block, and certificates appear, renew, and redirect HTTP to HTTPS without a single line of TLS configuration. This guide is the cookbook version — the handful of patterns that cover a homelab's entire proxy needs, and the two operational rules that keep it boring.
The whole job, in one file
jellyfin.example.com {
reverse_proxy jellyfin:8096
}
vault.example.com {
reverse_proxy vaultwarden:80
}
paperless.example.com {
reverse_proxy paperless:8000
}
That is a complete, production-grade config for three services: TLS issued and renewed per domain, HTTP→HTTPS redirects, HTTP/2 and websockets proxied automatically (no special-casing for Uptime Kuma-style socket-heavy apps). Run it from the official container with ports 80, 443, and 443/udp published — the UDP mapping is HTTP/3, keep it — and your services on the same Docker network so the container names resolve.
Rule one: persist /data. Rule two: see rule one
Caddy stores its certificates and ACME account in /data. Mount it (./data:/data) or every container recreation throws the certificates away and re-issues them — which works right up until Let's Encrypt's rate limit (5 duplicate certs per week) locks you out of your own domains for days. This is the single most common way people hurt themselves with Caddy, it happens during exactly the compose-file fiddling phase when you recreate containers constantly, and the fix is one volume line. Reloads, by contrast, are free: docker exec caddy caddy reload --config /etc/caddy/Caddyfile applies changes with zero downtime, and caddy validate checks the file before you bet on it.
Wildcard certs and internal-only services
Publishing *.home.example.com services that should never face the internet? Two useful patterns. First, a wildcard certificate via the DNS-01 challenge means one cert covers every subdomain and nothing appears in certificate-transparency logs announcing your service names — the trade-off is that DNS-01 needs a plugin for your DNS provider, which means a small custom Docker build (the one place Caddy costs more friction than Traefik; the TLS guide walks through it). Second, guard internal services by source address:
photos.home.example.com {
@internal remote_ip 192.168.0.0/16 100.64.0.0/10
handle @internal {
reverse_proxy immich:2283
}
respond 403
}
The second range is Tailscale's address space, so the rule reads "LAN and VPN only." For upload-heavy apps like Immich, add request_body { max_size 5GB } — Caddy's default is generous but not unlimited, and phone videos will find the ceiling.
Snippets stop the copy-paste
Once five services share the same headers or auth, define the block once and import it:
(secure) {
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
}
}
notes.example.com {
import secure
reverse_proxy trilium:8080
}
Between reverse_proxy, handle/matchers, import, and respond, you have read 95% of the Caddyfile vocabulary a homelab ever uses. The remaining 5% — file_server for static sites, basic_auth for quick gates, php_fastcgi — turns Caddy into the web server it also is, which is why a blog plus a dozen proxied apps can be one 40 MB process idling at 30 MB of RAM.
What I'd do
One Caddy container at the network edge, the Caddyfile in git, /data persisted, a snippet for shared headers, wildcard-plus-remote_ip for everything internal, and reload-not-restart as the habit. Check the config against caddy validate before every reload and Caddy becomes the component you forget exists — the correct emotional state for the thing holding all your TLS. How it stacks against the alternatives is in the reverse proxy showdown.
Compare Caddy
11 head-to-head comparisons.
Similar reverse proxy & gateways apps
Traefik
Reverse Proxy & GatewaysCloud-native reverse proxy and load balancer
Replaces HAProxy, AWS ELB
Pi-hole
Reverse Proxy & GatewaysBlackhole for Internet advertisements with a GUI for management
Replaces NextDNS
acme.sh
Reverse Proxy & GatewaysPure shell ACME client for TLS certificates
Replaces Certbot
mitmproxy
Reverse Proxy & GatewaysInteractive HTTPS proxy for inspection and debugging
Replaces Charles Proxy, Fiddler
Kong Gateway
Reverse Proxy & GatewaysCloud-native, fast, scalable API gateway
Replaces AWS API Gateway, Apigee
Istio
Reverse Proxy & GatewaysFull-featured service mesh for traffic and security control
Replaces AWS App Mesh