Introduction
Choosing a reverse proxy is one of the foundational decisions in any infrastructure. For over a decade Nginx has been the default answer. Caddy, with automatic HTTPS and a configuration file you can read aloud, has matured into a serious production contender. In 2026 both are actively maintained, fast, and feature-rich.
This article compares them across the dimensions that decide real deployments: certificate automation, configuration, performance, extensibility, and the operational cost of each.
Feature Comparison at a Glance
| Feature | Nginx | Caddy |
|---|---|---|
| Automatic HTTPS | No, needs Certbot or another ACME client | Yes, built in |
| Config format | Custom directive syntax | Caddyfile or JSON API |
| HTTP/3 (QUIC) | Since 1.25.0, still labelled experimental, not built by default | Built in, on by default with HTTPS |
| Config reload | nginx -s reload | Live JSON API, or reload |
| Plugin model | C modules, compile time | Go modules, compile time via xcaddy |
| Rate limiting | Built in (limit_req) | Third-party plugin |
| TCP/UDP proxying | Built in (stream) | Third-party plugin (layer4) |
| Memory footprint | Very low | Low |
| Ecosystem | Massive, decades deep | Smaller, growing |
Two rows there tend to decide the argument, and they are the two most guides get wrong. We will come back to both.
1. Automatic SSL: Caddy's Real Advantage
This is not a small convenience. It is the strongest argument in Caddy's favour and it is worth being concrete about why.
Caddy
example.com {
reverse_proxy localhost:3000
}
That is the whole file. Caddy obtains a certificate from Let's Encrypt or ZeroSSL on first request, renews it before expiry, handles the ACME challenge, and reloads itself. There is no cron job, no renewal hook, no separate binary, and no 2am incident because a renewal timer silently failed three months ago.
Nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Note listen 443 ssl; with a separate http2 on;. The old listen 443 ssl http2; form is deprecated as of nginx 1.25.1. Plenty of guides, and older versions of this one, still show the deprecated syntax.
Certbot works well. But you now own an ACME client, a renewal timer, a deploy hook to reload nginx, and the failure modes of all three. That is not hard, it is just permanently yours.
The gap widens with certificate count. One domain, the difference is a few minutes of setup. Fifty domains with dynamic hostnames, Caddy's on-demand TLS handles it with a config block while the Certbot path becomes an orchestration problem of its own.
2. Configuration Syntax, Honestly
Caddy's Caddyfile is genuinely simpler for the common case. But be careful with the example every comparison article reaches for:
# This does NOT work in a stock Caddy build.
example.com {
encode gzip zstd
rate_limit {
zone dynamic_zone {
key {remote_host}
events 100
window 1m
}
}
reverse_proxy localhost:3000
}
rate_limit is not a standard Caddy directive. It is a third-party plugin, and a stock caddy binary rejects that config with an unrecognized-directive error. To use it you build a custom binary:
# Rate limiting in Caddy means compiling it in
xcaddy build --with github.com/mholt/caddy-ratelimit
Nginx has had rate limiting built in for years:
limit_req_zone $binary_remote_addr zone=perip:10m rate=100r/m;
server {
location / {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://localhost:3000;
}
}
This is the honest shape of the comparison. Caddy is simpler until you need something outside its standard set, at which point you are compiling custom binaries with xcaddy and tracking plugin compatibility across upgrades. Nginx is more verbose but the batteries are already in the box: rate limiting, stream for TCP/UDP, and a module ecosystem that has been stress-tested for twenty years.
The same applies to TCP/UDP proxying: nginx's stream block is standard, Caddy's layer4 is a plugin.
3. Performance
In our testing with wrk on a 4-core VM serving a simple reverse-proxy workload:
| Metric | Nginx | Caddy |
|---|---|---|
| Requests/sec (HTTP) | ~48,000 | ~42,000 |
| Requests/sec (HTTPS) | ~38,000 | ~36,000 |
| P99 latency (HTTPS) | 2.1 ms | 2.4 ms |
| Memory at 10k conns | ~32 MB | ~58 MB |
Read that table with appropriate scepticism, including ours. Reverse-proxy benchmarks are enormously sensitive to worker counts, keepalive settings, TLS session resumption, cipher choice, kernel tuning, and what the backend is doing. A number from someone else's VM tells you the rough shape, not what you will get.
The shape is this: Nginx is somewhat faster and lighter, Caddy is close enough that it almost never decides the outcome. A 10 to 15 percent throughput difference matters if you are saturating a box at 40,000 requests per second. If you are serving 200 requests per second, which describes the overwhelming majority of production web apps, both are idle and the decision belongs to operations, not benchmarks.
The memory difference is real and worth noting for small VPS instances: Caddy is a Go binary with a garbage collector, nginx is C with an event loop. On a 512 MB box that gap is a consideration. On anything modern it is noise.
If throughput genuinely decides your architecture, benchmark your config on your hardware with your traffic pattern. Anything else is decoration.
4. HTTP/3 and QUIC
This is where most comparisons, and the earlier version of this article, contradict themselves. The precise status:
Nginx: QUIC and HTTP/3 support arrived in 1.25.0 and ships in nginx.org's official Linux binary packages. But nginx's own documentation still describes ngx_http_v3_module as experimental, and if you build from source it is not compiled by default, you need --with-http_v3_module. So: available, usable, packaged, and still formally experimental. Both "it's experimental" and "it's in the packages" are true, which is why you see both claims.
# nginx HTTP/3
server {
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Tell clients HTTP/3 is available
add_header Alt-Svc 'h3=":443"; ma=86400';
}
Caddy: HTTP/3 is built in and enabled automatically whenever HTTPS is active. There is nothing to configure and nothing to compile.
For HTTP/3 specifically, Caddy is simply ahead on ease.
5. Security Posture and Patch Cadence
An underrated dimension. Both are actively maintained and both ship security fixes, but their surfaces differ.
Nginx's C codebase carries the memory-safety failure modes that come with C. In July 2026 nginx patched three CVEs in one release, including a heap buffer overflow in map regex handling rated 9.2 critical that had been present since 2011. We covered that in detail in our note on the nginx map regex overflow. Caddy is written in Go, which eliminates that entire bug class (buffer overflows, use-after-free) at the cost of a garbage collector.
Do not read that as "Caddy is secure and nginx is not". Nginx's track record over twenty years at internet scale is genuinely strong, and the July release is the process working. But if memory-safety class bugs are a specific concern in your threat model, it is a real difference rather than a talking point.
The practical operational note: with nginx you are patching a system package on a schedule. With Caddy, if you have compiled plugins in via xcaddy, you are the distributor of your own binary and patching means rebuilding. That is a genuine ongoing cost that the "just one binary" pitch tends to omit.
6. When to Use Nginx
- You already run a large, battle-tested nginx configuration. Migrating working infrastructure for syntax aesthetics is not a project with a return.
- You need
streamfor TCP/UDP proxying, or OpenResty and Lua for request-time logic. - You need rate limiting, and you would rather it came in the box than in a custom build.
- Your team has deep nginx expertise. That expertise is worth more than any feature on this page.
- You are at genuinely high traffic where 10 percent throughput is real money.
7. When to Use Caddy
- You want certificates handled and never thought about again. This is the strongest single reason.
- Greenfield projects where minimal config gets you to production faster.
- Many domains, or dynamic hostnames where on-demand TLS solves a problem Certbot makes you orchestrate.
- You want HTTP/3 with no build flags or caveats.
- You want a JSON API to change config at runtime without a reload.
The Decision
| If this is true | Lean |
|---|---|
| Certificate management is your recurring pain | Caddy |
| You need rate limiting or TCP/UDP proxying out of the box | Nginx |
| Greenfield, small team, few services | Caddy |
| Existing nginx estate that works | Nginx |
| Dozens of dynamic hostnames | Caddy |
| Deep nginx or OpenResty expertise on the team | Nginx |
| Saturating hardware at very high RPS | Nginx |
| You do not want to compile your own proxy binary | Nginx |
Neither is wrong. Nginx remains the workhorse with an unmatched ecosystem and features that are simply present rather than plugged in. Caddy trades a slice of raw performance and a smaller ecosystem for dramatically simpler certificate operations, and for many teams that is the trade worth making.
Our honest default: Caddy for greenfield services where TLS automation is the recurring pain, nginx for anything complex, high-scale, or already working. The strongest argument against switching a functioning nginx setup is that it is functioning.
If you want help deciding, or migrating between them without a downtime window, that is what our server optimization and servers management work covers.
Sources
Talk to the engineer who will own your stack.
No account managers, no offshore handoff. Senior DevOps, direct. Tell us what you are dealing with and you get a straight answer.
Related Articles
The Ultimate Guide to Linux Server Management in 2025
A comprehensive guide to modern Linux server management covering automation, containerization, cloud integration, AI-driven operations, security best practices, and essential tooling for 2025.
Server & DevOpsFixing "421 Misdirected Request" for Plesk Sites on Ubuntu 22.04 After Apache Update
Resolve the 421 Misdirected Request error affecting all HTTPS sites on Plesk for Ubuntu 22.04 after an Apache update, caused by changed SNI requirements in the nginx-to-Apache proxy chain.
Server & DevOpsHow to Set Up GlusterFS on Ubuntu
A complete guide to setting up a distributed, replicated GlusterFS filesystem across multiple Ubuntu 22.04 nodes, including installation, volume creation, client mounting, maintenance, and troubleshooting.