Olympus Docs
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

ProxyProsCons
NginxUbiquitous, fast, vast ecosystemTLS 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).
EnvoyModern, fast, observability-friendlyHeavy config burden (xDS, YAML), more of a service mesh component than a simple proxy.
HAProxyBattle-tested for load balancingTLS config is verbose; no built-in ACME.
TraefikAuto-discovers services in Docker/K8sReload semantics are surprising; ACME works but quirky in HA.
CaddyAutomatic 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.yml builds 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.

On this page