ADRs
0009, Caddy over Nginx
Why Olympus uses Caddy as its reverse proxy
Status: Accepted Date: 2026-02 Stakeholders: Bobby Nannier
Context
Olympus needs a reverse proxy with: TLS termination (automated Let's Encrypt), per-IP rate limiting, security headers, easy config, healthchecks. Single host.
Alternatives
| Proxy | Pros | Cons |
|---|---|---|
| Nginx | Ubiquitous, fast, vast ecosystem | TLS config is manual (cert files, renewal cron). Rate limiting via ngx_http_limit_req_module is functional but config is verbose. Health-check directives only in Plus (paid). |
| Envoy | Modern, fast, observability-friendly | Heavy config burden (xDS, YAML), more of a service mesh component than a simple proxy. |
| HAProxy | Battle-tested for load balancing | TLS config is verbose; no built-in ACME. |
| Traefik | Auto-discovers services in Docker/K8s | Reload semantics are surprising; ACME works but quirky in HA. |
| Caddy | Automatic Let's Encrypt by default. Compact config. caddy-ratelimit module gives per-IP limits. JSON API for runtime config. Go binary, no deps. | Smaller community than Nginx. Custom build needed for the rate-limit module. |
Decision
Caddy. Specifically, a custom build that includes caddy-ratelimit for per-IP throttling.
Consequences
- TLS "just works", operators don't manage cert files or renewal cron.
- The Caddyfile is short and readable. The full prod config is ~170 lines (see Reference, Caddyfile).
- Custom build needs a reproducible workflow:
caddy-build.ymlbuilds the image and publishes to GHCR. See Security, Caddy Supply Chain. - The DNS-01 ACME flow (needed if Caddy is behind a proxy that intercepts HTTP) is supported via Cloudflare/Route53/etc plugins.