Installed April 19, 2026 on edge01 VPS (172.93.50.184). Replaces Pangolin/Traefik as the VPS’s reverse proxy and public web server.
Caddy is the single reverse proxy fronting public-facing services on the VPS. It handles:
- Automatic HTTPS โ cert issuance and renewal via Let’s Encrypt. No certbot, no cron jobs, no manual work forever.
- Static file serving โ hosts the Bee Hub at
troglodyteconsulting.com. - Reverse proxy โ routes subdomains to LAN services via NetBird mesh (once NetBird is set up).
- HTTP/3 support โ out of the box on port 443/udp.
- Cloudflare DNS-01 challenge โ for wildcard certs on
*.edmd.me(via the custom build with the Cloudflare DNS module).
| Version | v2.11.2 |
| Custom build | Yes (xcaddy + github.com/caddy-dns/cloudflare) |
| Binary | /usr/bin/caddy |
| Config | /etc/caddy/Caddyfile |
| Env file | /etc/caddy/cloudflare.env (CF_API_TOKEN) |
| Data dir | /var/lib/caddy/ |
| Web root | /var/www/bee-hub/ |
| Service | systemctl {status,reload,restart} caddy |
| Logs | journalctl -u caddy |
When you add a site block to the Caddyfile:
n8n.troglodyteconsulting.com {
reverse_proxy 192.168.8.100:5678
}
On systemctl reload caddy, Caddy:
- Notices the domain is public and has no cert yet
- Contacts Let’s Encrypt via ACME
- Proves ownership โ HTTP-01 challenge (default) or DNS-01 (for wildcards)
- Receives and installs the cert
- Starts serving HTTPS on 443
- Redirects HTTP โ HTTPS automatically
All of that in seconds. Renewals (at 30 days remaining) happen silently in the background. You never touch certs again.
What Caddy handles forever:
- Initial cert request
- Automatic renewal
- OCSP stapling
- Fallback from Let’s Encrypt to ZeroSSL if LE is down
- Modern TLS (1.3, correct ciphers, HSTS)
- Certificate hot-reload without dropping connections
Every site block is independent. The starter Caddyfile looks like:
{
# Global options
email doctor@edwarddelgrosso.com
}
# Main Bee Hub site โ static files
troglodyteconsulting.com, www.troglodyteconsulting.com {
root * /var/www/bee-hub
file_server
encode gzip zstd
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}
# Subdomain reverse-proxy (requires NetBird mesh)
# n8n.troglodyteconsulting.com {
# reverse_proxy 192.168.8.100:5678
# }
# Wildcard via Cloudflare DNS-01 (needs CF_API_TOKEN)
# *.edmd.me {
# tls {
# dns cloudflare {env.CF_API_TOKEN}
# }
# @lidarr host lidarr.edmd.me
# handle @lidarr { reverse_proxy 192.168.8.100:8686 }
# }
# Catch-all 404 for unknown Host headers
:80, :443 {
respond "Not configured" 404
}
Adding a new service is three lines:
newservice.troglodyteconsulting.com {
reverse_proxy 192.168.8.100:PORT
}
Then: systemctl reload caddy. Cert issued within seconds, service live.
For *.edmd.me wildcard certs, Caddy uses DNS-01 challenge via Cloudflare’s API.
- API Token lives in
/etc/caddy/cloudflare.envasCF_API_TOKEN=...(mode 600, caddy:caddy ownership) - Scope โ Edit zone DNS on both
edmd.meandtroglodyteconsulting.comzones - Referenced in Caddyfile as
{env.CF_API_TOKEN}inside thetlsblock:
*.edmd.me {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
# ... site blocks ...
}
Creating a new token โ dash.cloudflare.com/profile/api-tokens, pick “Edit zone DNS” template, select the target zones.
Rotation โ after replacing the token, systemctl reload caddy picks up the new env file.
Test config before reload (always):
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
Reload (graceful, no dropped connections):
systemctl reload caddy
Watch cert issuance in real time:
journalctl -u caddy -f | grep -iE 'cert|acme|tls'
List loaded modules (including cloudflare):
caddy list-modules | grep -iE 'cloudflare|dns'
Certificates stored in:
/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/
Common troubleshooting:
| Symptom | Likely cause | Fix |
|---|---|---|
| HTTP 404 on domain but DNS resolves | Site block missing from Caddyfile | Add block, reload |
| Cert issuance fails | Port 80 blocked OR DNS hasn’t propagated | Check UFW, dig the domain |
{env.CF_API_TOKEN} is empty |
cloudflare.env not loaded by systemd |
Check systemd unit EnvironmentFile= directive |
| Reload silently fails | Syntax error in Caddyfile | caddy validate first |
| Slow first request after reload | Cert being issued | Check journal; second request is fast |
- No certbot, ever. Caddy manages all ACME interactions internally. Any certbot tutorial you find online does not apply.
- Port 80 must be open for HTTP-01 challenge. UFW allows it on edge01.
- Cloudflare proxy (orange cloud) is OK โ DNS-01 doesn’t require the domain to resolve directly to the VPS. HTTP-01 requires it though, so prefer DNS-01 when Cloudflare proxy is on.
- Caddyfile syntax is indentation-loose but block braces matter. Always
caddy validatebefore reload. - Env vars in Caddyfile use
{env.VAR_NAME}syntax โ note the dot separator, not underscore. - Reverse-proxying to LAN services (192.168.8.x) only works over NetBird. Without the mesh, the VPS can’t reach LAN IPs.
- HTTP/3 works out of the box once port 443/udp is open in UFW. No extra config.
systemctl reloadvsrestartโ always prefer reload. Restart drops in-flight connections; reload does graceful handoff.- Deploy from Mac uses
/Users/bee/Sync/ED/homelab/bee_hub/deploy-vps.shโ now targetingroot@172.93.50.184:/var/www/bee-hub. Cron runs every 30 min. - Cloudflare token rotation after any suspected exposure. Template: Edit zone DNS, both zones.
| Caddy | nginx + certbot | Traefik | |
|---|---|---|---|
| Cert mgmt | Automatic, zero config | Manual (certbot + cron) | Automatic |
| Config lines per site | ~3 | ~15-20 | YAML, more verbose |
| Systemd unit | 1 | 2 (nginx + certbot.timer) | 1 |
| HTTP/3 | Out of the box | Requires build flags | Config flag |
| Docker-native | No (but doesn’t need to be) | No | Yes, via labels |
| Best for | Self-hosted reverse proxy, mixed static + reverse-proxy | Heavy custom rewrite rules, fine-grained caching | Docker Compose stacks with many services |
Chose Caddy because edge01 is primarily a reverse proxy for a handful of LAN services plus the static Bee Hub site. Caddy’s zero-config HTTPS eliminates the certbot-renewal maintenance burden that plagued the previous Traefik/Pangolin setup.