Skip to content

Commit 8b6e361

Browse files
committed
docs: [#272] Add ADRs for HTTPS implementation decisions (Phase 9)
Phase 9: Documentation ADRs created: - caddy-for-tls-termination.md: Why Caddy v2.10 was chosen over Pingoo/nginx - per-service-tls-configuration.md: Why domain+use_tls_proxy pattern vs nested tls section - uniform-http-tracker-tls-requirement.md: Why all HTTP trackers must use same TLS setting Key rationale documented: - Caddy: WebSocket support (Pingoo failed), automatic HTTPS, simple config - use_tls_proxy naming: Avoids confusion with tracker's native TslConfig - Uniform TLS: Tracker's on_reverse_proxy is global, not per-tracker This completes issue #272 - all 9 phases are now done.
1 parent 6948342 commit 8b6e361

5 files changed

Lines changed: 621 additions & 8 deletions

File tree

docs/decisions/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This directory contains architectural decision records for the Torrust Tracker D
66

77
| Status | Date | Decision | Summary |
88
| ------------- | ---------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
9+
| ✅ Accepted | 2026-01-20 | [Caddy for TLS Termination](./caddy-for-tls-termination.md) | Use Caddy v2.10 as TLS proxy for automatic HTTPS with WebSocket support |
10+
| ✅ Accepted | 2026-01-20 | [Per-Service TLS Configuration](./per-service-tls-configuration.md) | Use domain + use_tls_proxy fields instead of nested tls section for explicit TLS opt-in |
11+
| ✅ Accepted | 2026-01-20 | [Uniform HTTP Tracker TLS Requirement](./uniform-http-tracker-tls-requirement.md) | All HTTP trackers must use same TLS setting due to tracker's global on_reverse_proxy |
912
| ✅ Accepted | 2026-01-10 | [Hetzner SSH Key Dual Injection Pattern](./hetzner-ssh-key-dual-injection.md) | Use both OpenTofu SSH key and cloud-init for debugging capability with manual hardening |
1013
| ✅ Accepted | 2026-01-10 | [Configuration and Data Directories as Secrets](./configuration-directories-as-secrets.md) | Treat envs/, data/, build/ as secrets; no env var injection; users secure via permissions |
1114
| ✅ Accepted | 2026-01-07 | [Configuration DTO Layer Placement](./configuration-dto-layer-placement.md) | Keep configuration DTOs in application layer, not domain; defer package extraction |
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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

Comments
 (0)