|
| 1 | +# Decision: Caddy for TLS Termination |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Accepted |
| 6 | + |
| 7 | +## Date |
| 8 | + |
| 9 | +2026-01-20 |
| 10 | + |
| 11 | +## Context |
| 12 | + |
| 13 | +The Torrust Tracker Deployer needed automatic HTTPS support for all HTTP services: |
| 14 | + |
| 15 | +- Tracker REST API |
| 16 | +- HTTP Tracker(s) |
| 17 | +- Grafana monitoring UI |
| 18 | +- Health Check API |
| 19 | + |
| 20 | +Key requirements: |
| 21 | + |
| 22 | +1. **Automatic certificate management** - No manual certificate generation or renewal |
| 23 | +2. **WebSocket support** - Grafana Live requires WebSocket connections |
| 24 | +3. **Simple configuration** - Minimize operational complexity |
| 25 | +4. **Docker-friendly** - Easy integration in Docker Compose deployments |
| 26 | +5. **Production-ready** - Mature and reliable for production use |
| 27 | + |
| 28 | +## Decision |
| 29 | + |
| 30 | +We adopted **Caddy v2.10** as the TLS termination proxy for all HTTP services. |
| 31 | + |
| 32 | +### Implementation |
| 33 | + |
| 34 | +Caddy is deployed as a Docker container in the same Docker Compose stack, serving as a reverse proxy for all HTTP services that need HTTPS: |
| 35 | + |
| 36 | +```yaml |
| 37 | +services: |
| 38 | + caddy: |
| 39 | + image: caddy:2.10 |
| 40 | + ports: |
| 41 | + - "80:80" # HTTP (ACME challenges) |
| 42 | + - "443:443" # HTTPS |
| 43 | + - "443:443/udp" # HTTP/3 (QUIC) |
| 44 | + volumes: |
| 45 | + - ./storage/caddy/etc/Caddyfile:/etc/caddy/Caddyfile:ro |
| 46 | + - caddy_data:/data # TLS certificates |
| 47 | + - caddy_config:/config |
| 48 | +``` |
| 49 | +
|
| 50 | +Configuration uses Caddy's simple Caddyfile format: |
| 51 | +
|
| 52 | +```caddyfile |
| 53 | +{ |
| 54 | + email admin@example.com |
| 55 | +} |
| 56 | + |
| 57 | +api.example.com { |
| 58 | + reverse_proxy tracker:1212 |
| 59 | +} |
| 60 | + |
| 61 | +grafana.example.com { |
| 62 | + reverse_proxy grafana:3000 |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +## Consequences |
| 67 | + |
| 68 | +### Positive |
| 69 | + |
| 70 | +- **Zero-configuration HTTPS**: Certificates are automatically obtained and renewed via Let's Encrypt |
| 71 | +- **WebSocket support**: Caddy handles WebSocket upgrades transparently (no special configuration) |
| 72 | +- **Simple configuration**: Caddyfile is ~21 lines vs hundreds for nginx+certbot |
| 73 | +- **HTTP/3 support**: QUIC protocol support is included by default |
| 74 | +- **Hot reload**: Configuration changes apply without service interruption |
| 75 | +- **Production-proven**: Caddy has been stable since 2015 with large community |
| 76 | + |
| 77 | +### Negative |
| 78 | + |
| 79 | +- **Larger binary**: ~40MB vs ~4MB for Pingoo or ~1MB for nginx |
| 80 | +- **Post-quantum cryptography requires custom build**: Standard Caddy doesn't include PQC, but can be compiled with [Cloudflare's Go fork (CFGo)](https://github.com/cloudflare/go) to support X25519Kyber768 key exchange. Pingoo includes X25519MLKEM768 by default. |
| 81 | +- **Additional container**: One more service in the Docker Compose stack |
| 82 | + |
| 83 | +### Neutral |
| 84 | + |
| 85 | +- **Certificate storage**: Requires persistent volume (`caddy_data`) for certificates |
| 86 | + |
| 87 | +## Alternatives Considered |
| 88 | + |
| 89 | +### 1. Pingoo (Rust-based TLS proxy) |
| 90 | + |
| 91 | +**Evaluated in**: [Issue #234](https://github.com/torrust/torrust-tracker-deployer/issues/234) |
| 92 | + |
| 93 | +**Rejected because**: Pingoo strips the `Upgrade` HTTP header, breaking WebSocket connections required by Grafana Live. |
| 94 | + |
| 95 | +```rust |
| 96 | +// From Pingoo source (http_proxy_service.rs): |
| 97 | +let dominated_headers = &[ |
| 98 | + "host", |
| 99 | + "upgrade", // ← WebSocket upgrade stripped! |
| 100 | + "connection", |
| 101 | + ... |
| 102 | +]; |
| 103 | +``` |
| 104 | + |
| 105 | +**Result**: Experiments 1-3 (HTTP) succeeded, but Experiment 4 (Grafana WebSocket) failed. |
| 106 | + |
| 107 | +**Upstream issue filed**: [pingooio/pingoo#23](https://github.com/pingooio/pingoo/issues/23) |
| 108 | + |
| 109 | +### 2. nginx + certbot |
| 110 | + |
| 111 | +**Traditional approach** with manual configuration. |
| 112 | + |
| 113 | +**Rejected because**: |
| 114 | + |
| 115 | +1. **Manual setup**: Must run certbot manually to generate first certificate |
| 116 | +2. **Cron-based renewal**: Requires bash script/cronjob for certificate renewal |
| 117 | +3. **Complex configuration**: ~200+ lines of nginx.conf for SSL, headers, locations |
| 118 | +4. **WebSocket configuration**: Requires explicit `proxy_set_header Upgrade` and `Connection` headers |
| 119 | +5. **Multi-domain complexity**: Each subdomain needs separate certificate management |
| 120 | + |
| 121 | +### 3. Traefik |
| 122 | + |
| 123 | +**Alternative reverse proxy** with automatic HTTPS. |
| 124 | + |
| 125 | +**Not evaluated** because Caddy already met all requirements with simpler configuration. |
| 126 | + |
| 127 | +## Related Decisions |
| 128 | + |
| 129 | +- [prometheus-integration-pattern.md](./prometheus-integration-pattern.md) - Prometheus is enabled by default |
| 130 | +- [grafana-integration-pattern.md](./grafana-integration-pattern.md) - Grafana requires Prometheus dependency |
| 131 | + |
| 132 | +## References |
| 133 | + |
| 134 | +- [Issue #270 - Evaluate Caddy for HTTPS Termination](https://github.com/torrust/torrust-tracker-deployer/issues/270) |
| 135 | +- [Issue #272 - Add HTTPS Support with Caddy](https://github.com/torrust/torrust-tracker-deployer/issues/272) |
| 136 | +- [Issue #234 - Pingoo Evaluation](https://github.com/torrust/torrust-tracker-deployer/issues/234) |
| 137 | +- [Caddy Official Documentation](https://caddyserver.com/docs/) |
| 138 | +- [Caddy GitHub Repository](https://github.com/caddyserver/caddy) |
| 139 | +- [Let's Encrypt Documentation](https://letsencrypt.org/docs/) |
| 140 | +- [Go Post-Quantum with Caddy](https://sam-burns.com/posts/go-post-quantum-with-caddy/) - Tutorial on compiling Caddy with Cloudflare's Go fork for PQC support |
0 commit comments