Caddy Reverse Proxy
Caddy configuration for TLS termination, HTTP/3, www redirect, and reverse proxying to the Next.js container.
The Caddyfile
The entire Caddy configuration is 8 lines in infra/Caddyfile:
trovella.ai {
reverse_proxy web:3000
encode gzip zstd
}
www.trovella.ai {
redir https://trovella.ai{uri} permanent
}
This handles:
- TLS termination -- Caddy automatically obtains and renews Let's Encrypt certificates for
trovella.ai. No certbot, no cron job, no manual certificate management. - HTTP to HTTPS redirect -- Caddy automatically redirects HTTP (port 80) to HTTPS (port 443) for any configured domain.
- Reverse proxying -- all HTTPS traffic to
trovella.aiis forwarded to thewebcontainer on port 3000 via the Docker internal network. - Compression -- responses are compressed with gzip or zstd based on client support.
- www redirect --
www.trovella.aipermanently redirects (301) totrovella.aiwith the original URI preserved.
How TLS Works
Caddy uses the ACME HTTP-01 challenge to obtain certificates from Let's Encrypt. The flow:
- Caddy listens on port 80 and 443
- Let's Encrypt sends a challenge request to
http://trovella.ai/.well-known/acme-challenge/<token> - Caddy responds with the proof token
- Let's Encrypt issues the certificate
- Caddy stores the certificate in the
caddy-dataDocker volume - Caddy automatically renews before expiration (typically every 60-90 days)
This requires Cloudflare DNS to be in DNS-only mode (grey cloud, no Cloudflare proxy) so that HTTP-01 challenges reach Caddy directly. If Cloudflare proxy mode were enabled, Cloudflare would terminate TLS and the ACME challenge would fail.
HTTP/3 Support
The Docker Compose configuration exposes port 443 for both TCP and UDP:
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
Caddy advertises HTTP/3 support via the Alt-Svc response header. Clients that support QUIC (Chrome, Firefox, Edge) will upgrade to HTTP/3 automatically. The UDP port exposure is what enables this -- without it, HTTP/3 would be silently unavailable.
Why Caddy Over Alternatives
| Feature | Caddy | Nginx + certbot | Traefik |
|---|---|---|---|
| Automatic TLS | Built-in, zero config | Requires certbot + cron | Built-in |
| Configuration | ~10-line Caddyfile | 50+ line nginx.conf + sites-available | Docker labels or TOML |
| HTTP/3 | Built-in | Requires recompilation with quic patch | Built-in |
| Docker service discovery | Not needed (fixed services) | Not needed | Primary use case |
| AI agent familiarity | Lower | Higher (more common) | Medium |
Caddy was chosen because the Caddyfile is shorter than the equivalent Nginx configuration including certbot setup. Traefik's Docker label-based routing adds unnecessary complexity when the set of services is fixed. The trade-off is that AI agents are more likely to generate Nginx-specific configuration, but the 8-line Caddyfile is simple enough that this has not been a practical issue.
What Caddy Does Not Do
Caddy is not configured to proxy admin dashboards. Inngest (port 8288) and Drizzle Studio (port 4983) are accessed only via SSH tunnel:
# Inngest dashboard
gcloud compute ssh trovella-prod-vm \
--zone=us-central1-a --project=trovella-prod \
--tunnel-through-iap -- -L 8288:localhost:8288
# Then open http://localhost:8288
This is intentional -- keeping these off the public internet avoids the need for authentication middleware on internal tooling. See Networking for the full IAP SSH access model.
Certificate Persistence
TLS certificates and OCSP staples are stored in the caddy-data Docker volume. This volume survives container restarts and docker compose down. If the volume is deleted, Caddy re-obtains certificates on next startup (within Let's Encrypt rate limits -- 5 duplicate certificates per week).
Deployment
The Caddyfile is SCPed to the VM during each deploy:
gcloud compute scp infra/Caddyfile $VM_NAME:~ --tunnel-through-iap
# Then moved to /opt/trovella/Caddyfile
Caddy watches the Caddyfile for changes and reloads automatically when docker compose up -d recreates the container. TLS certificates are preserved in the named volume across container recreations.
Future Considerations
- Cloudflare proxy mode -- enabling the orange cloud in Cloudflare would add CDN, DDoS protection, and WAF. This would require switching from HTTP-01 to DNS-01 ACME challenges (Caddy supports this with a Cloudflare API token plugin).
- Additional domains -- adding subdomains (e.g.,
api.trovella.ai) would be additional blocks in the Caddyfile. - Rate limiting -- Caddy supports rate limiting via the
rate_limitdirective if application-level rate limiting proves insufficient.