Short version for the impatient: if you run one to five small sites on a single VPS, use Caddy. If you already have nginx configs that work, or you need something Caddy makes awkward, stay on nginx and stop reading comparison posts at midnight. I’m saying this as someone who defaults to nginx out of muscle memory and keeps catching himself doing extra work for no reason.
The question comes up every time I set up a box for a client. Somebody says “just put nginx in front of it,” and I nod, and then I spend forty minutes on certbot, a redirect server block, and a cron job I will forget about. None of that is hard. It’s just a lot of small steps where each one can go wrong quietly. So I made myself sit down and write out what actually differs between Caddy and nginx for the boring, common case: one VPS, a few apps, a reverse proxy, HTTPS.
This is not a benchmark post. I didn’t run wrk against either one, and anyone who tells you the request-per-second gap matters for a client site doing 40 requests a minute is selling something. It’s a post about configuration, certificates, and the day-two chores that eat your evenings.
What each one does out of the box
Caddy turns on HTTPS by default. The automatic HTTPS docs say it provisions certificates for your sites, renews them, and redirects HTTP to HTTPS without extra setup. You write a hostname in the config, and Caddy goes and gets a certificate from a public ACME CA such as Let’s Encrypt. That’s the whole feature, and it’s the one that changes how a small server feels.
nginx does not do this. You install certbot or acme.sh, run it once, wire the certificate paths into a server block, add a second server block that redirects port 80, and make sure renewal reloads nginx afterwards. Every one of those is a thing I have forgotten at least once. The usual failure is a renewal that works while the reload hook doesn’t, so the cert on disk is fresh and the one nginx is serving is ninety days old.
Here is the same job in both. First, the nginx version for one app on port 3000, after certbot has done its part:
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;
location / {
proxy_pass http://127.0.0.1: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;
}
}
And the Caddy version of the same thing:
app.example.com {
reverse_proxy 127.0.0.1:3000
}
Two lines. Caddy’s reverse_proxy directive passes the usual forwarding headers for you, and the HTTP redirect comes from automatic HTTPS. I don’t want to oversell it, because the nginx file is longer mostly because it’s explicit, and explicit has real value when something breaks. But for a site I’ll touch twice a year, I’d rather have the two-line one.
Where nginx is still the better pick
I don’t want this to read like a Caddy fan post, because I’d be lying. There are cases where I still reach for nginx.
The first is existing knowledge and existing config. If a client has an nginx setup that has run for three years, ripping it out to save eleven lines is a bad trade. The risk is real and the benefit is cosmetic. I have a rule for this: I only migrate a working proxy when I’m already touching it for another reason.
The second is the sheer volume of answers. When something odd happens with nginx, somebody on Stack Overflow hit it in 2016 and wrote down the fix. Caddy has good docs and an active forum, but the pile of accumulated answers is smaller. That matters more at 2 a.m. than any feature table.
The third is tuning that Caddy hides from you. nginx makes you choose buffer sizes, worker counts, and keepalive behaviour in the open. Caddy picks sensible defaults and mostly keeps them out of your way. If you’re the kind of person who wants to know exactly why a response took 80ms longer, nginx hands you more dials. I’m still not sure how many of those dials I would ever turn on a client box, to be honest. Probably fewer than I think.
The fourth, and the one people miss, is health checks. The nginx upstream module docs say that a dynamically configurable group with periodic health checks is part of the commercial subscription. Open source nginx has passive checks (it marks a server as failed after errors), but active probing is a paid feature. Caddy’s reverse_proxy lists active and passive health checks as part of the directive itself. If you run two backends and want the proxy to notice a dead one before a user does, that gap is concrete.
Day-two work is where Caddy wins
Initial setup is a one-time cost, so it’s the wrong place to compare. The real difference shows up over months.
With Caddy, adding a new site is a new block in one file and a reload. A certificate comes with it. With nginx, adding a new site means a new server block, a new certbot run, a test, and a reload. Neither is slow. But I have a growing pile of client servers, and the Caddy ones are the ones I never think about. That is the highest compliment I can pay infrastructure.
Config reloads are close to a tie. nginx gives you nginx -t before a reload, which I like, and Caddy has caddy validate plus caddy reload. Both refuse to load a broken file and keep serving the old config, which is the behaviour you want.
Logging is where I’d give nginx a slight edge on familiarity. The access log format is something every sysadmin has seen. Caddy logs structured JSON by default, which is great if you pipe it into something and mildly annoying if you just want to tail -f and read it with your eyes. You can change the format, but you have to know to look.
Here is a slightly more realistic Caddyfile, with two apps and some compression, the kind I’d actually deploy:
{
email [email protected]
}
app.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
api.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080 127.0.0.1:8081 {
health_uri /healthz
health_interval 10s
}
}
That second block spreads requests across two backends (the default policy picks one at random per request) and probes /healthz every ten seconds. Getting the same behaviour from open source nginx means a third-party module or a paid plan.
What about running it behind a tunnel or a platform
Two situations change the answer, and I’ve hit both.
If the server sits behind a Cloudflare Tunnel, the public certificate question mostly disappears, because Cloudflare terminates TLS at the edge. I wrote about that setup in how I stopped opening ports for clients. In that case Caddy’s headline feature matters much less, and the choice comes down to config style and health checks. I’d still pick Caddy for the shorter files, but the margin is thin.
If you deploy through a platform that manages the proxy for you, the question is moot. Tools like Coolify and Dokploy run their own reverse proxy and you rarely touch it. My notes on that are in what actually differs after migrating a client. Don’t hand-roll a proxy on top of a platform that already ships one.
A way to decide this week
Here’s the decision rule I’ve settled on after going back and forth for a while. Count the sites on the box. If it’s under about ten, you’re starting fresh, and nobody on the team has nginx muscle memory worth protecting, use Caddy. If you have working nginx configs, or you need active health checks without paying, or the person who will maintain it after you knows nginx cold, stay on nginx and write the certbot renewal hook down somewhere you will find it.
If you want to test this without committing, spin up a throwaway VPS, point a spare subdomain at it, and install Caddy with your package manager. Write the two-line Caddyfile above, run caddy run, and watch it fetch a certificate on its own. It takes about five minutes. If it feels like cheating, that’s the point. I do this kind of infrastructure work for clients through my consulting practice, and the setups that need the fewest repeat visits are nearly always the ones with the fewest moving parts.