Obiz Solutions

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