What is Caddy?
Caddy is a web server and reverse proxy written in Go. Its headline feature is automatic HTTPS: point a domain at the server, name that domain in the config, and Caddy obtains and renews a Let’s Encrypt certificate on its own — no certbot, no cron job, no manual renewal.
A whole site in three lines
A complete reverse proxy config, with HTTPS included:
example.com {
reverse_proxy app:3000
}
That’s the entire Caddyfile. Caddy listens on ports 80 and 443,
redirects HTTP to HTTPS, handles the certificate, and forwards every
request to the app listening on port 3000.
Why put a reverse proxy in front of an app
- TLS in one place: the app speaks plain HTTP internally; Caddy terminates HTTPS at the edge.
- One public entry point: only ports 80/443 are exposed — the app and database stay on a private network.
- Room to grow: compression, caching, extra routes, or a second app behind another hostname are config changes, not code changes.
Caddy with Docker Compose
In a Compose setup, Caddy is just another service. Containers reach each
other by service name (app above), and a volume keeps the certificates
across restarts so they aren’t re-issued every deploy:
caddy:
image: caddy:2-alpine
ports: ["80:80", "443:443"]
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
The one gotcha: client IPs
Behind a proxy, the app sees every request coming from Caddy’s internal
IP, not the visitor’s. Caddy forwards the real address in the
X-Forwarded-For header, and the app must be told to trust it — otherwise
anything keyed on client IP, like login rate limiting, treats all visitors
as one. In Fastify that’s the trustProxy option (see
What is Fastify?).
Caddy vs. Nginx
Nginx is faster to tune at very large scale and has a bigger ecosystem of recipes. Caddy wins on simplicity: a small config, sane defaults, and TLS that just works. For a single app on a single VPS, Caddy is usually the shorter path.
Further reading
Official docs: caddyserver.com/docs