Both run on essentially any entry-level VPS, both terminate TLS, both will happily front your Next.js app or static site. The question is never “which web server is better.” It’s which one costs you less time and pain for your specific self-hosting setup. This is that trade-off, honestly laid out for the person running box number one, not for a platform team.
The one thing Caddy wins on outright: automatic HTTPS
Caddy’s headline feature is real and it is the single biggest time-saver for a solo operator. When it knows the domain it is serving, Caddy provisions a TLS certificate from a public ACME CA such as Let’s Encrypt or ZeroSSL, keeps renewing it, and redirects HTTP to HTTPS automatically - no extra configuration and no separate tooling.[1] Caddy’s own docs describe it as the first web server to use HTTPS automatically and by default.[1]
Concretely, a working HTTPS site is this:
example.com {
reverse_proxy 127.0.0.1:3000
}
Point example.com’s A/AAAA record at the box, open ports 80 and 443, and Caddy handles cert issuance, renewal, and the HTTP-to-HTTPS redirect for you.[1]
Nginx is not worse at TLS - it is just not automatic. You manage the certificate lifecycle yourself with Certbot. The certbot --nginx path is a two-step handshake: Certbot obtains the cert and then modifies your Nginx server configuration to install it and automate renewal on a schedule.[2] The nginx plugin works for most setups, automates both obtaining and installing, and supports rollback with certbot --nginx rollback - but it is editing your config, and the docs recommend backing up before you run it.[2]
The practical difference: with Nginx + Certbot, HTTPS is a repeatable process you occasionally babysit; with Caddy, it is default behavior. For a single VPS where you just want the lock icon to work and never think about it again, Caddy saves you from the first setup and from every future “why did my cert expire” midnight. Nginx + a certbot renew cron does the same job, but the TTY is on you.
Config: Caddyfile vs nginx.conf
This is where the philosophy gap shows. Caddy uses the Caddyfile - a small declarative format where the site address comes first and directives follow, and a handful of built-in features mean the happy path is short.[3] Nginx uses directive blocks in nginx.conf and site files, where almost everything - including sensible headers and TLS settings - is something you opt into.
The same reverse proxy in each:
# Caddyfile
api.example.com {
reverse_proxy /api/* 127.0.0.1:8000
file_server
}
# nginx.conf (site block)
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://127.0.0.1:8000;
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;
}
}
The Caddyfile gets you HTTPS for free, so you write the proxy and move on. The Nginx block needs the header forwarding added by hand - a habit that matters, because without X-Forwarded-Proto an app behind TLS happily builds http:// redirects. If you are already comfortable writing Nginx server blocks (and we have covered the complete hardened shape separately in the Nginx reverse proxy config guide), nothing above is hard - it is just more typing per site.
There is a real cost to Caddy’s brevity, though: the Caddyfile is a separate, opinionated abstraction. If you need behavior it does not map cleanly to, you drop below it and work in JSON config instead - a context switch you rarely hit in Nginx because the config is already “the config.”
Performance: let’s be honest
Short version: on a busy box, Nginx tends to be faster on raw static-file throughput and on simple reverse-proxying, and it tends to use less memory while doing it. This is the community and engineering consensus, and it exists for a reason: Nginx is a C program written for high concurrency over two decades of hardening, while Caddy is a Go binary that makes different trade-offs in favor of extensibility and safety.
We are deliberately not quoting benchmark numbers here. We have not measured Caddy versus Nginx on our own rented accounts, and per our sourcing standard we do not publish figures we cannot back with a measured run. Treat any specific “Caddy X requests/s vs Nginx Y” number you see in a blog post as suspect unless the author shows their test methodology. Anecdotally and in practice, on a loaded VPS Nginx wins the throughput contest; for a side project doing a few hundred requests a second it does not matter.
Caddy does bring concrete operational advantages that are not raw speed: it ships as a statically compiled binary with no runtime library dependencies - the Caddy docs note it generally does not even need libc - and being written in Go it is not vulnerable to the class of C memory-safety bugs that historically hit Nginx and other C servers.[4] That is a real argument, not marketing: fewer moving parts and memory-safe defaults are worth something on a box you manage alone.
Features that actually come up in self-hosting
Rate limiting. Nginx has this built in via ngx_http_limit_req_module, a leaky-bucket limiter keyed on whatever you want - typically the client IP - with shared-memory zones and burst/nodelay control.[5]
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
}
}
Caddy, notably, does not ship rate limiting in the core binary. There is an official-registry rate_limit module (http.handlers.rate_limit, from a community project), but it is explicitly not included with Caddy - you build it in via xcaddy or select it on the download page.[6] So a common pain point that is one directive in Nginx is a rebuild in Caddy. For anything you need to genuinely protect from abuse, this is a real point for Nginx.
Load balancing and health checks. Both handle multiple upstreams. Nginx groups servers in an upstream block with weighted round-robin by default, per-server max_fails / fail_timeout to mark a dead peer, and backup servers for failover.[7]
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass http://backend;
}
}
Caddy’s reverse_proxy does the same with multiple upstreams, plus explicit health checking via health_uri and health request headers.[8]
example.com {
reverse_proxy node1:80 node2:80 {
health_uri /health
fail_duration 30s
max_fails 3
}
}
One honest caveat on Nginx here: true active periodic health checks (the health_check directive) are part of the commercial subscription, not the open-source build.[9] Open-source Nginx gives you passive failover - mark a peer dead only after requests to it fail. For a single-VPS self-hosting setup you rarely need more than that.
WebSockets and SSE. Both proxy these fine. Nginx needs the explicit HTTP/1.1 + Upgrade headers and proxy_read_timeout tuned so idle sockets do not die; Caddy handles upgrade natively with far less ceremony. Caddy makes them feel free; Nginx makes you write the upgrade block once and then you never look at it.
Ecosystem and extensibility
Nginx’s superpower is its plugin world - especially OpenResty / Lua scripting, which lets you do complex request manipulation without leaving the server. If your hobby grows into something with nontrivial per-request logic (auth, caching, traffic shaping), the Nginx ecosystem is decades deep and battle-tested. The cost is that you are now managing a C-based server plus possibly a custom build.
Caddy’s extensibility is cleaner on paper - it is Go, and plugins are Go modules you compile in at build time, which is why the whole thing is a single static binary.[4] For most self-hosters that means: fewer dependencies than a full Nginx + Lua stack, easier upgrades (“replace the binary”), and a smaller attack surface. Caddy runs in Docker the same way Nginx does, and both are first-class in the usual distro repos - no winner there.
So when do you choose which?
Choose Caddy when: it is a side project or a handful of personal services on one VPS; you want TLS to just work; you are serving something with one upstream and do not need fancy per-location logic; you value a single binary and memory-safe defaults; or you would rather spend your evening writing the app than writing server config.
Choose Nginx when: you are already invested in an Nginx stack or know it cold; you are chasing maximum static/RPS throughput on a loaded box; you need built-in rate limiting; you plan non-trivial request logic (OpenResty / Lua); or you are running several services and already have a site/template approach you reuse.
There is also a pragmatic middle answer a lot of self-hosters land on: Nginx fronting everything (because it is fast and you know it), with the cert handled by Certbot on a renewal job - or Caddy if you would genuinely rather never think about certificates again. Both are completely reasonable. What is not reasonable is pretending one is universally better.
Sources
- Caddy - Automatic HTTPS (default HTTPS, ACME issuance, auto-renewal, HTTP-to-HTTPS redirect). https://caddyserver.com/docs/automatic-https - checked 2026-10-17
- Certbot - User Guide, using plugins (the nginx plugin obtains and installs, modifies server config, rollback). https://eff-certbot.readthedocs.io/en/stable/using.html - checked 2026-10-17
- Caddy - Caddyfile quick-start (site-address-first declarative format). https://caddyserver.com/docs/quick-starts/caddyfile - checked 2026-10-17
- Caddy - Features (static binary, no libc, written in Go, memory safety). https://caddyserver.com/features - checked 2026-10-17
- nginx - Module ngx_http_limit_req_module (leaky-bucket rate limiting). https://nginx.org/en/docs/http/ngx_http_limit_req_module.html - checked 2026-10-17
- Caddy - Module http.handlers.rate_limit (not included in Caddy by default; added via xcaddy). https://caddyserver.com/docs/modules/http.handlers.rate_limit - checked 2026-10-17
- nginx - Module ngx_http_upstream_module (weighted round-robin, max_fails, fail_timeout, backup). https://nginx.org/en/docs/http/ngx_http_upstream_module.html - checked 2026-10-17
- Caddy - reverse_proxy directive (multiple upstreams, health check via health_uri). https://caddyserver.com/docs/caddyfile/directives/reverse_proxy - checked 2026-10-17
- nginx - Module ngx_http_upstream_hc_module (active
health_checkis part of the commercial subscription). https://nginx.org/en/docs/http/ngx_http_upstream_hc_module.html - checked 2026-10-17