Why I Ended Up on Caddy — and What It Actually Does on Our Servers

I didn't choose Caddy from a comparison table. I learned web servers the way I learned most things on a server: by breaking them. Nginx first, then Apache, then LiteSpeed, then Varnish, then H2O for a while. Eventually I found Caddy — a web server whose config reads almost like plain English — and I've stayed on it ever since.

That's the whole origin story, and I won't dress it up with benchmarks I never ran. What's more useful is the practical part: what Caddy actually does for us today, on the two servers I'm writing this from, both running Caddy 2.11. Here are the patterns, straight from the real config files.

1. One app, one block, one line

Most of our apps are small Python or Node services listening on a local port. Putting one on the internet with HTTPS is this:

app.example.com { reverse_proxy localhost:5005 } 

That's a real block from our config, with only the domain changed. Caddy takes care of TLS and proxies everything to the app. For a solo builder this is the single biggest reason to stay: adding a new product to the server is a three-line change, not an afternoon.

2. Wildcard subdomains in a single block

24ad.info gives every country its own subdomain. In Caddy, the bare domain and every subdomain share one site block:

24ad.info, *.24ad.info { encode zstd gzip # … } 

Because the main sites sit behind Cloudflare, they don't use Caddy's automatic certificates — they use Cloudflare Origin CA certificates, pointed to with a tls line. Caddy's automatic HTTPS is the default; we just override it where Cloudflare is in front.

3. Caching headers where they belong

Caddy is where we decide how long browsers and Cloudflare may keep things:

  • Hashed build assets, uploaded images, globe textures: public, max-age=31536000, immutable — a full year, because their file names change whenever their content does.
  • The service worker: deliberately not immutable, so browsers pick up new versions.
  • APIs: no-store.
  • App downloads (our FieldFlow Android builds): Content-Disposition: attachment and no-store, so the browser downloads the file instead of trying to display it.

4. Compression in one place

Sites are compressed with encode zstd gzip. On BreathTime we went one step further in October: we switched compression off in the Next.js app and let Caddy do it, so the work happens once, at the edge of the server, for every response.

5. The ordering gotcha with handle

One lesson is now written as a comment in our Caddyfile, in capitals. handle blocks are mutually exclusive — the first one that matches wins. So a rule that blocks hidden files (anything starting with a dot) has to be its own handle block, placed before the catch-all handle { file_server }. Put it after, and the file server has already answered by the time your rule would have run.

If you use Caddy as a static file server, check this one today.

6. Logs without secrets

Our blog engine authenticates writes with an API key in a request header. Access logs are useful; access logs full of API keys are a liability. Caddy's log filter deletes that header before anything is written:

log { output file /var/log/caddy/blog-access.log { roll_size 20MiB roll_keep 10 roll_keep_for 2160h } format filter { request>headers>X-Api-Key delete } } 

Logs also roll over automatically: 20 MB per file, ten files, ninety days.

7. What Caddy shouldn't do

Not everything belongs in the web server. BreathTime's Content Security Policy used to be a static header in Caddy. When we moved to a strict policy with a fresh nonce on every request, it had to move into the app — a static header can't change per request. Knowing where Caddy's job ends is part of using it well.

The honest summary

Caddy isn't magic, and I can't tell you it's faster than Nginx — I've never measured that. What I can tell you is that our config files are short enough to read in one sitting, and each block does one obvious thing. When you run several products on your own servers with nobody else to ask at 11pm, that's the property that matters.

Slawomir Luzny
Founder, FixFlex LTD
West London