Caddy Reverse Proxy - Caddyfile Patterns and Automatic HTTPS
Status: Active
Last Updated: 2026-08-26
Category: Networking - Reverse Proxies
Prerequisites: reverse-proxy-basics, cloudflare-dns
Time: 3 hours
Tags: caddy, caddyfile, https, dns-01, wildcard, cloudflare, reverse-proxy
Summary
Practical Caddyfile patterns for the fogserv edge: site blocks per service, automatic Let's Encrypt issuance, and wildcard certificates via the Cloudflare DNS-01 plugin so internal names work without public challenges. Caddy's selling point is that correct TLS is the default, not a configuration project.
🎯 What You'll Learn
By the end of this article, you'll be able to:
- ✅ Write site blocks with reverse_proxy, headers, and basic auth
- ✅ Build Caddy (or use a custom image) with the Cloudflare DNS module
- ✅ Issue and renew wildcard certs via DNS-01 automatically
- ✅ Serve both proxied (Cloudflare) and LAN-direct hostnames from one instance
Table of Contents
- Context / Why This Matters
- Caddyfile Fundamentals
- Automatic HTTPS
- Wildcard via Cloudflare DNS-01
- Useful Directives Cheat Sheet
- Troubleshooting & Common Pitfalls
- Next Steps / Ops Actions
- Sources & Related
- Change Log
Context / Why This Matters
The fogserv fleet has two kinds of HTTPS names: public ones behind Cloudflare proxying, and LAN-only ones (nas.fogserv.cloud, proxmox.fogserv.cloud) resolved by split-horizon DNS (dhcp-dns-split-horizon). HTTP-01 can't cover the LAN-only names (no public route to :80), so we standardize on DNS-01 with a wildcard, issued and renewed entirely by Caddy.
Caddyfile Fundamentals
/etc/caddy/Caddyfile:
{
email ops@fogserv.cloud
}
# Public service, backend on same host
grafana.fogserv.cloud {
reverse_proxy 127.0.0.1:3000
}
# Public service, backend on another LAN host
git.fogserv.cloud {
reverse_proxy 10.10.0.11:3000
}
# Static files
status.fogserv.cloud {
root * /var/www/status
file_server
}
That's a complete working config — site address implies automatic HTTPS. Validate and reload:
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy # zero-downtime config swap
Automatic HTTPS
By default Caddy:
- Obtains an ACME certificate (HTTP-01 + TLS-ALPN-01) for each site name.
- Renews at ~2/3 of lifetime.
- Redirects HTTP→HTTPS for you.
This works out of the box for publicly routable names. It fails for wildcard names (* requires DNS-01) and for LAN-only names if your firewall blocks inbound 80/443 to the ACME challenge — hence DNS-01 below.
Wildcard via Cloudflare DNS-01
Stock caddy binaries don't include the Cloudflare DNS provider; build with xcaddy or use the docker image lucaslorentz/caddy-docker-proxy style builds. Simplest reliable path: xcaddy build.
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddyserver/cloudflaredns
sudo mv caddy /usr/local/bin/caddy && sudo chmod +x /usr/local/bin/caddy
Token: the acme-dns01 scoped token from cloudflare-dns, in /etc/caddy/cf-token (0600, owned by caddy user). Then:
{
email ops@fogserv.cloud
}
*.fogserv.cloud, fogserv.cloud {
tls {
dns cloudflare {env.CF_API_TOKEN}
resolvers 1.1.1.1
}
# Route by subdomain inside the wildcard block:
@grafana host grafana.fogserv.cloud
handle @grafana {
reverse_proxy 127.0.0.1:3000
}
@nas host nas.fogserv.cloud
handle @nas {
reverse_proxy https://10.10.0.31:5006 {
transport http {
tls_insecure_skip_verify # NAS self-signed upstream
}
}
}
handle {
respond "not found" 404
}
}
# systemd unit must pass the token into env
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
Environment=CLOUDFLARE_API_TOKEN_FILE=/etc/caddy/cf-token
Environment=CLOUDFLARE_DNS_API_TOKEN_FILE=/etc/caddy/cf-token
One cert now covers every current and future subdomain — adding a service means only editing routing, never cert config.
Useful Directives Cheat Sheet
reverse_proxy 127.0.0.1:3000 # basic proxy
reverse_proxy h2c://10.10.0.5:9000 # proxy to cleartext HTTP/2 backend
basicauth {
user $2a$14$hashedpasswordhere # caddy hash-password
}
header {
Strict-Transport-Security "max-age=31536000"
-Server # hide server header
}
encode gzip zstd # compression
handle_path /old/* { # strip prefix then proxy
reverse_proxy 127.0.0.1:8080
}
log {
output file /var/log/caddy/access.log
}
Practical Examples
Add a service end-to-end (wildcard already in place):
# 1. DNS (public): create grafana CNAME → edge, proxied (cloudflare API/dashboard)
# 2. Routing: add one handle block above, reload
caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy
# 3. Verify
curl -s https://grafana.fogserv.cloud/api/health | jq .
openssl s_client -connect grafana.fogserv.cloud:443 -servername grafana.fogserv.cloud </dev/null 2>/dev/null \
| openssl x509 -noout -subject -dates # shows *.fogserv.cloud cert
LAN-only name: no DNS record publicly needed at all — just split-horizon entry + matching host matcher. Cert still valid because wildcard covers it.
Troubleshooting & Common Pitfalls
| Problem | Cause | Fix |
|---|---|---|
tls: no certificate available for new name |
Name added but outside wildcard block | Put all names under the *.fogserv.cloud block or add explicit site block |
| DNS-01 fails: unauthorized/rate limited | Token lacks DNS Edit, or hit LE duplicate-cert limit | Scope check (cloudflare-dns); staging endpoint while testing (acme_ca global option) |
| Works externally, cert warning on LAN | LAN resolver returns different IP but name matches cert — actually fine; warning means old cert cache | Trust via wildcard; clear client state |
| Port 443 bind fails after install | Something else (nginx) owns it | One edge proxy per host; see nginx-configuration migration note |
| Backend sees client IP as edge IP | Missing trusted-proxies config in app | Set app trusted proxy = edge IP (reverse-proxy-basics) |
| Reload drops connections | Using restart instead of reload |
Use systemctl reload / caddy reload |
Next Steps / Ops Actions
- Compare with nginx/Traefik approaches: nginx-configuration, traefik-v3-reverse-proxy
- Front it with Cloudflare properly: cloudflare-dns
- Harden TLS settings: ../security/tls-configuration
Sources & Related
External references consulted:
- https://caddyserver.com/docs/caddyfile
- https://github.com/caddyserver/cloudflaredns
- https://caddyserver.com/docs/automatic-https
Related knowledge-base articles:
Change Log
2026-08-26
- Initial creation: Caddyfile patterns, DNS-01 wildcard setup, directive cheat sheet.