Two Caddy deployments: the VPS (edge01) for public web serving, and CT103 (caddy) on Proxmox for internal *.edmd.me reverse proxying with wildcard HTTPS.
| VPS Caddy | CT103 Caddy (internal) | |
|---|---|---|
| Host | edge01 (172.93.50.184) |
CT103 @ 192.168.8.54 |
| Role | Public web server | Private reverse proxy |
| Domains | troglodyteconsulting.com |
*.edmd.me (41 services) |
| Cert issuance | HTTP-01 (port 80) | DNS-01 via Cloudflare |
| Reachable from | Public internet | LAN + NetBird peers only |
| Installed | April 19, 2026 | April 19, 2026 |
Both use the same custom Caddy build: v2.11.2 + github.com/caddy-dns/cloudflare module, built via xcaddy.
Every LAN service has a clean HTTPS URL with real Let’s Encrypt certs. Accessible from any device on the LAN or via NetBird mesh; DNS A records in Cloudflare point *.edmd.me โ 192.168.8.54.
Media & Arr Stack โ plex.edmd.me, calibre.edmd.me, lidarr.edmd.me, sonarr.edmd.me, radarr.edmd.me, prowlarr.edmd.me, bookshelf.edmd.me, audiobookshelf.edmd.me (alias: audiobooks.edmd.me), navidrome.edmd.me (alias: music.edmd.me), kiwix.edmd.me (alias: wiki.edmd.me)
Automation & Monitoring โ n8n.edmd.me, kuma.edmd.me (alias: uptime.edmd.me), gotify.edmd.me (alias: notify.edmd.me), grafana.edmd.me (alias: metrics.edmd.me), prometheus.edmd.me, dozzle.edmd.me (alias: logs.edmd.me)
Reading & Content โ freshrss.edmd.me (alias: rss.edmd.me), wallabag.edmd.me (alias: read.edmd.me), immich.edmd.me (alias: photos.edmd.me), shelfmark.edmd.me, aurral.edmd.me
Utilities โ convertx.edmd.me (alias: convert.edmd.me), flaresolverr.edmd.me, homepage.edmd.me (alias: home.edmd.me)
Infrastructure Admin โ portainer.edmd.me, proxmox.edmd.me (aliases: hpve.edmd.me, pve.edmd.me), cockpit.edmd.me, pihole.edmd.me (alias: dns.edmd.me)
Identity & Auth โ auth.edmd.me (Authentik SSO โ forward-domain auth for all services above)
Total: 42 service URLs (26 primary hostnames + 16 aliases).
Uses a single wildcard site block *.edmd.me with named matchers (@service) and handle directives. One wildcard cert covers every subdomain, so adding a new service is three lines:
@newservice host newservice.edmd.me
handle @newservice {
reverse_proxy 192.168.8.100:PORT
}
Then add an A record newservice.edmd.me โ 192.168.8.54 in Cloudflare and systemctl reload caddy. Cert is already covered by the existing wildcard.
Multi-host matchers use space-separated hostnames (not commas โ Caddy treats the comma as part of the name):
@kuma host kuma.edmd.me uptime.edmd.me
Backends with self-signed certs (Portainer on 9443, Proxmox on 8006, Cockpit on 9090) require tls_insecure_skip_verify:
reverse_proxy https://192.168.8.100:9443 {
transport http {
tls_insecure_skip_verify
}
}
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.