Vivian Voss

The Caddy Replacement

caddy nginx web unix

The Replacement ■ Episode 01

“Your entire reverse proxy configuration fits in a tweet. The interesting question is why it ever needed more.”

The year is 2015. You have just deployed a web application. It works. Then someone mentions HTTPS. You install Certbot. You configure Nginx. You add a cron job for certificate renewal. You write a renewal hook to reload Nginx after rotation. You test it. It fails. You debug the hook. You discover the cron schedule collides with log rotation. You fix it. You write documentation. You go home. You do not sleep well.

Matt Holt had the same evening in 2015. Unlike the rest of us, he decided the problem was not the configuration. The problem was that configuration existed at all. He wrote Caddy : a web server that handles TLS automatically, negotiates certificates from Let’s Encrypt on first request, renews them silently, and does not require you to understand the ACME protocol, set a cron job, or write a single line of TLS configuration. One binary. Zero ceremony.

What You Replace

The standard HTTPS reverse proxy stack in 2024 consists of four moving parts: Nginx for the proxy itself, Certbot for certificate issuance, a cron job for renewal, and a post-renewal hook to reload the server. Each is maintained separately. Each can fail independently. Each has its own configuration syntax, its own logging, and its own set of assumptions about the state of the machine beneath it.

With Caddy, the entire arrangement collapses into one file:

example.com {
    reverse_proxy localhost:3000
}

That is not a simplified excerpt. That is the complete configuration. HTTPS is the default, not an add-on. Certificates are obtained automatically via the ACME protocol, renewed before expiry, and applied without a restart. HTTP/3 is enabled out of the box since version 2.5 in 2022. No flag. No module. No conference talk explaining why you should enable it.

The HTTPS Reverse Proxy Stack Before Nginx Reverse proxy + TLS termination Certbot Certificate issuance (ACME) Cron Scheduled renewal trigger Renewal Hook Reload Nginx after rotation 4 components, 4 configs, 4 failure modes After Caddy Reverse proxy Auto HTTPS (ACME) Auto renewal HTTP/3 Hot reload 1 binary, 1 config, 1 process Config that fits in a tweet: example.com { reverse_proxy localhost:3000 }

133,000 websites run on Caddy. That number is modest next to Nginx’s 38.6 per cent market share, which is rather the point. Nginx has twenty years of Stack Overflow answers, Lua scripting in the request path, and the inertia of an industry that has collectively memorised nginx -t && nginx -s reload. Caddy has correctness by default. The market prefers the former. Engineering prefers the latter.

The Performance Question

The objection arrives on schedule: “But Caddy is written in Go. Go has garbage collection. Garbage collection means latency. Nginx is C. C is fast.” The objection is not wrong. It is merely irrelevant for 99 per cent of deployments.

The benchmarks tell a straightforward story. Below 50,000 requests per second, which covers the vast majority of production services, the difference between Caddy and Nginx is effectively noise. Above that threshold, Nginx pulls ahead by roughly 10 to 15 per cent, attributable to Go’s garbage collector pausing at precisely the moments a reverse proxy would prefer it did not.

Throughput: Caddy vs Nginx Throughput Requests per second 0 20k 50k 80k 100k+ Practically identical 50k req/s Caddy Nginx 10-15% gap (Go GC pauses) Below 50k req/s: choose by features. Above: measure.

Fifty thousand requests per second is not a typical Tuesday afternoon. It is the kind of traffic that implies a dedicated infrastructure team, a load balancer in front of the proxy, and an architecture that has long since stopped worrying about the configuration syntax of its edge server. If your service handles that volume, you already know what you need. If it does not (and for most services it does not) the performance objection is academic.

What You Lose

Intellectual honesty demands the counter-argument. You lose three things by choosing Caddy over Nginx, and all three are real.

Lua scripting in the request path. Nginx with OpenResty allows you to run Lua at every stage of the HTTP lifecycle: rewriting headers, transforming bodies, querying Redis mid-request. Caddy has no equivalent. If your architecture requires programmable logic inside the proxy, Nginx remains the correct tool.

Raw throughput at extreme scale. Above 50,000 requests per second, C wins. Go’s garbage collector introduces micro-pauses that compound under saturation. At cloud-native hyperscaler volume, those pauses matter. At anything below that: they do not.

Twenty years of institutional knowledge. Every Nginx problem has been solved on Stack Overflow. Every edge case has a blog post. Every misconfiguration has a three-year-old thread with the correct answer buried in the second reply. Caddy’s documentation is excellent, but the community surface area is smaller. When you are debugging at 3 AM, accumulated knowledge has a value that no benchmark can measure.

What You Gain

You gain something no amount of Nginx tuning provides: the elimination of an entire class of operational failure.

Certificate expiry is the most common cause of HTTPS outages in production. Not misconfiguration. Not cipher suite incompatibility. Forgetting to renew. Certbot’s cron job did not fire. The renewal hook had a typo. The certificate renewed but Nginx was not reloaded. The monitoring check passed because it tested HTTP, not HTTPS. Each of these failures has brought down production systems. Each is entirely preventable. Caddy prevents all of them by removing the human from the renewal loop entirely.

You also gain HTTP/3 and QUIC without lifting a finger. Nginx added experimental HTTP/3 support in 2022 behind a compile flag, a configuration directive, and a prayer. Caddy has shipped it as a default since version 2.5. The protocol is simply there, working, for every domain you serve.

The Trade-off What you lose Lua scripting Programmable request pipeline Peak throughput >50k req/s 10-15% advantage (C vs Go GC) 20 years of Stack Overflow Every edge case, solved twice What you gain Auto HTTPS Zero TLS config. Default since 2015. Auto renewal No Certbot. No cron. No hooks. HTTP/3 out of the box Default since v2.5 (2022) Config that fits in a tweet Human error surface: near zero The 10% you lose at extreme scale buys the 100% you gain in operational reliability.

When Nginx Stays

There are legitimate reasons to keep Nginx. Three, specifically.

Your architecture runs Lua in the request path. OpenResty is not a convenience. It is a dependency. Rewriting it in Caddy middleware is possible but expensive, and the result is less capable. Nginx stays.

Your traffic exceeds 50,000 requests per second consistently, and the 10 to 15 per cent throughput differential has measurable impact on your tail latency. You have measured this. You have not merely assumed it. Nginx stays.

Your team has 15 years of Nginx muscle memory, and the migration cost exceeds the operational cost of maintaining Certbot. This is a human engineering decision, not a technical one, and it is entirely valid. Nginx stays.

For everything else, and “everything else” covers the overwhelming majority of production deployments, Caddy is the replacement. Not the alternative. The replacement.

The Replacement

Caddy did not add features to the reverse proxy. It removed problems from it. TLS is not configured because it does not need configuring. Certificates are not managed because management implies the possibility of forgetting. HTTP/3 is not enabled because it was never disabled. The configuration is three lines because the problem is three lines.

The entire philosophy of Caddy is an implicit rebuke of the industry’s habit of solving simple problems with complex tools and then celebrating the complexity as craftsmanship. The HTTPS reverse proxy is a solved problem. The solution is one binary, one Caddyfile, and the quiet confidence of software that does its job without requiring yours.

You do not need Nginx, Certbot, Cron, and a renewal hook. You need a web server that handles HTTPS. Caddy has been that server since 2015. The only thing missing was the industry’s permission to use it.

Consider this your permission.