Comparison
Caddy or Nginx? The config file decides this one
The verdict
For a new deployment on a server you own - a VPS, a couple of apps, some static sites - use Caddy. Automatic HTTPS is not a convenience feature, it is the removal of an entire class of 3am incident, and a working reverse proxy with a valid certificate is three lines of config. Use Nginx when you already run it, when you need modules or directives Caddy has no answer for, when you stream large files, or when you are genuinely serving enough traffic per box that a memory footprint measured in single-digit megabytes matters. Below roughly 50,000 requests per second on a single host - which is almost everyone - the performance argument is noise and the operational one is everything.
Both projects are healthy and actively released: Nginx shipped its 1.30 stable branch with the 1.31 mainline carrying QUIC and HTTP/3 work through mid-2026, and Caddy is in the 2.11 line after 2.10 added Encrypted ClientHello and post-quantum key exchange by default. Neither is a risky choice. This comparison is about which one costs you less attention over the next three years.
Nginx vs Caddy on the dimensions that decide it
Treat every published throughput number here as directional. Independent benchmarks disagree wildly depending on payload size, protocol, and core count: some tests put Nginx ahead by a comfortable margin on 1 KB static files, others show Caddy winning by around 20 percent on HTTP/2 on many-core ARM. The consistent findings are the memory numbers and the streaming behaviour, not the raw request rate.
| Dimension | Nginx | Caddy |
|---|---|---|
| TLS certificates | Bring your own. Certbot or acme.sh plus a renewal timer plus a reload hook, all of which can fail silently. | Automatic. Issues, renews, and hot-reloads certificates itself, including on-demand issuance for wildcard-style multi-tenant hosts. |
| Config to first working proxy | Roughly 25 lines once you add the TLS block, the proxy headers, and the HTTP-to-HTTPS redirect server. | Three lines. Proxy headers and the redirect are defaults rather than things you must remember. |
| Memory footprint | Single-digit megabytes at idle. Still the leader when you are packing many instances onto small nodes. | Tens of megabytes, because it is a Go binary with a garbage collector. Irrelevant on a 2 GB VPS, relevant at high density. |
| Large file streaming | Clear winner. It does not buffer response bodies, so gigabyte downloads and video do not inflate memory. | Fine for normal traffic, measurably hungrier on sustained multi-gigabyte transfers. |
| HTTP/3 | Supported in mainline and production-ready, but it is a build and configuration decision you make deliberately. | On by default alongside HTTP/2, with no extra configuration. |
| Extensibility | Enormous module ecosystem, plus Lua and njs scripting. Twenty years of answers exist for whatever you need. | Go modules compiled into a custom binary via xcaddy. Clean, but a smaller catalogue and a build step. |
| Operations | Reload with a signal. Config errors are caught by a test command you have to remember to run. | Admin API for zero-downtime config changes, and a JSON config layer underneath the Caddyfile for programmatic control. |
| Exit cost | Moderate. A mature Nginx estate accumulates rewrites, maps, and Lua that do not translate mechanically. | Low. A Caddyfile is short enough to rewrite as Nginx config in an afternoon. |
The same reverse proxy in both config languages
One HTTPS host in front of an app listening on port 3000, with compression, WebSocket upgrade, and a redirect from plain HTTP. This is the single most common self-hosting configuration there is.
Caddyfile
app.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
} That is the complete file. Caddy obtains and renews the certificate, redirects port 80 to 443, sets the X-Forwarded headers, and negotiates HTTP/3 without being asked.
Caddy: static files and a second app
www.example.com {
root * /srv/site
encode zstd gzip
file_server
header /assets/* Cache-Control "public, max-age=31536000, immutable"
}
api.example.com {
reverse_proxy 127.0.0.1:8080 {
health_uri /healthz
lb_try_duration 5s
}
} Nginx server block
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
gzip on;
gzip_types text/css application/javascript application/json;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
} Plus a certbot installation, a renewal timer, and a reload hook that has to fire for the new certificate to be served. None of that is hard; all of it is one more thing that can be quietly broken for two months.
Gotcha: the four proxy_set_header lines are not optional decoration. Omit them and your application sees every request as coming from 127.0.0.1 over plain HTTP, which breaks rate limiting, audit logs, and any framework that builds absolute URLs from the request. Caddy sets the equivalent headers by default, which is a fair summary of the whole comparison.
When each server is the right call
Choose Caddy when
- You are standing up a new VPS and want HTTPS working before you have finished your coffee.
- Nobody on the team is a full-time systems engineer, and config you can read aloud is worth more than a benchmark.
- You host many domains, especially customer-supplied ones, where on-demand certificate issuance removes an entire subsystem.
- You want HTTP/3 and modern TLS defaults without researching which of them to turn on.
- You reconfigure routing programmatically and want an admin API rather than templating a file and sending a signal.
Choose Nginx when
- You already have a working estate. Rewriting years of accumulated maps, rewrites, and rate limits buys you nothing.
- You serve large media or software downloads, where not buffering response bodies is a hard memory advantage.
- You run at density: dozens of instances per node, or containers where tens of megabytes of resident memory each actually adds up.
- You need something specific from the module ecosystem - ModSecurity, njs, an obscure upstream protocol - that Caddy has no equivalent for.
- Your certificates come from an internal CA or a corporate process rather than ACME, which removes Caddy's biggest advantage.
- Your team's existing operational muscle memory, monitoring, and log parsing are all built around Nginx.
A note on the middle ground: many teams now terminate TLS at a CDN or a load balancer and run a much simpler origin server behind it. If Cloudflare or a cloud load balancer already holds your certificates, Caddy's headline feature is neutralised and the choice collapses back to familiarity. That is a perfectly good reason to keep Nginx.
Official documentation: caddyserver.com/docs and nginx.org/en/docs. Both have excellent references; Caddy's is shorter because there is less to describe.
Where this fits in a self-hosted stack
A reverse proxy is one layer of a self-hosted deployment. The self-hosting guide covers the rest: the VPS itself, Coolify or Dokploy for deployments, backups, and the point at which a managed platform stops being more expensive than your time.
Related: the Docker vs Podman comparison for what runs behind the proxy, the bash and Linux cheatsheet for the systemctl and curl commands you will use to debug it, and the HTTP status code reference for reading the access log. Managed alternatives are in the hosting directory.