Caddy vs Nginx is the question almost everyone runs into when they set up their own server and need a web server or reverse proxy in front of it. The short version: Nginx is faster, leaner, and has been the industry standard for twenty years. Caddy is much simpler, obtains HTTPS certificates entirely on its own, and ships with safer defaults. Which one is better almost never comes down to speed. It comes down to how many sites you run and how much time you want to spend on configuration.
We are in an unusually good position to answer this. Our own server has run entirely on Caddy for months, with 139 sites in a single main configuration file and close to a thousand automatically managed certificate files. We know Nginx from years of shops and client servers. For this article we put both side by side on the same machine in 2026 and measured them ourselves instead of copying someone else’s benchmark. The result was more one-sided than we liked as Caddy users, and we are still sticking with Caddy. The reason is further down.
HomeSec Explorer compares Caddy, Nginx Proxy Manager and Traefik from a homelab perspective using real Let’s Encrypt certificates. A good overview before we get into the numbers.
Caddy vs Nginx: The Short Answer
If you only read one paragraph:
- Pick Caddy if you run several small to mid-sized services across many domains, want HTTPS without thinking about it, and want to still understand your configuration a year from now. For self-hosting, homelabs, agency servers with lots of client sites and side projects, Caddy is the more relaxed choice.
- Pick Nginx if performance per dollar genuinely matters (very high traffic, lots of static files), if your team already knows Nginx, if you need a module that only exists for Nginx, or if your host, your framework docs and your monitoring tools all assume Nginx.
- Both are production-ready. The days when Caddy was considered a toy are long gone. This page is being served by Caddy right now.
If you are one step earlier and wondering what a reverse proxy even does, our article What Is a Reverse Proxy? explains the idea from scratch. And if you are actually torn between the two classics, Apache vs Nginx is the comparison you want.
What Caddy and Nginx Actually Are
Nginx (pronounced “engine-x”) was written by Igor Sysoev in the early 2000s to solve the so-called C10k problem: ten thousand simultaneous connections on one server without starting a process for each. Nginx is written in C and is event-driven. A master process manages several worker processes, usually one per CPU core, and each worker handles thousands of connections at once. Today Nginx belongs to F5, with a free open-source edition and the commercial Nginx Plus.
Caddy is much younger. Matt Holt released it in 2015, and the current version 2 arrived in 2020 as a complete rewrite. Caddy is written in Go and had one goal from day one that was radical at the time: HTTPS by default, not as an extra chore. As soon as you put a domain name in the configuration, Caddy obtains a certificate itself, renews it in time, and redirects HTTP to HTTPS. There is no Certbot, no cron job, and no renewal to forget.
Both handle the same core work: serving static files, passing requests to applications as a reverse proxy, load balancing across backends, compressing responses, and talking to PHP via FastCGI. The difference is not what they can do but how much you have to do yourself and what it costs.
| Caddy | Nginx | |
|---|---|---|
| Released | 2015 (v2: 2020) | 2004 |
| Language | Go (memory-safe) | C |
| License | Apache 2.0 | BSD (2-clause) |
| Automatic HTTPS | Yes, built in | No, needs Certbot or similar |
| HTTP/3 | On by default | Since 1.25, must be enabled |
| Configuration | Caddyfile or JSON API | Own syntax in nginx.conf |
| Change config at runtime | Yes, via admin API | Reload only |
| Plugins | Compiled in (xcaddy) | Dynamic modules |
| Community and tutorials | Growing | Huge |

Our Setup: Why We Use Caddy at All
On a single VPS we run lots of small services: this website, tools, dashboards, test environments, APIs for our own apps, a handful of static project pages. Each gets its own subdomain. Counted on the day this article was written:
- 1,027 lines of Caddyfile, 824 of them with actual content (excluding blank lines and comments)
- 139 site blocks, i.e. domains or subdomains
- 100
reverse_proxydirectives - 991 certificate files under
/var/lib/caddy - 85 certificates issued or renewed successfully in the last 30 days according to the journal
- 28 days straight without restarting the Caddy process, at about 100 MB of memory
We never touched those 85 certificates a month. Not one line of Certbot, no cron job, no “your certificate is about to expire” email. With Nginx, each of those 139 sites would be its own server block with its own ssl_certificate lines, and Certbot would need to know every single domain. That works, thousands of servers run that way, but it is work that comes back with every new subdomain.
That is the honest reason for our choice: not speed, but the number of domains. For a single large application the advantage would be small. At 139, it is the difference between “new subdomain in two minutes” and “new subdomain plus certificate management.”
Configuration: Caddyfile vs nginx.conf
The most visible difference between Caddy and Nginx is the configuration. Here is a complete reverse proxy with HTTPS for a Node app on port 3000. Caddy first:
app.example.com {
reverse_proxy 127.0.0.1:3000
}
Three lines, and they already include: a certificate from Let’s Encrypt or ZeroSSL, automatic renewal, an HTTP-to-HTTPS redirect, HTTP/2 and HTTP/3, and sensible X-Forwarded-* headers.
The same thing in Nginx, at the level you actually need in practice:
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_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;
}
}
Plus a one-off certbot --nginx -d app.example.com and a check that the renewal timer is running. About twenty lines instead of three. The important part: the Nginx version is not worse, it is just more explicit. Every line is there because you wanted it. That is both its strength and its weakness. You see exactly what happens, but you also need to know exactly what is missing.

For our benchmark below we needed an identical job for both: one path for static files, everything else reverse-proxied to a backend. Without HTTPS, so TLS would not muddy the result. The Caddy config was 12 lines, five of which were just the global block that turns off the admin API and automatic HTTPS for the test. The Nginx config was 22 lines, even though we kept it as short as possible. In our experience, that rough one-to-two ratio holds across almost every setup.
Caddy’s JSON API
Something that often gets lost in comparisons: in Caddy the Caddyfile is just a convenient front end. Internally everything is translated to JSON, and Caddy has an admin API (by default only on localhost:2019) that lets you read and change the configuration at runtime, without restarts and without dropped connections. For platforms that create customer domains automatically, that is a real advantage. With Nginx you write files and trigger a reload. That is also seamless, but it is a detour through the file system.
Caddy vs Nginx Benchmark: Our Own Measurements
Most benchmarks online are either years old or come from someone selling one of the two. So we measured ourselves. Conditions first, because numbers without them are worthless.
How we measured
- Machine: our development VPS with 12 vCPUs and 23 GB RAM, with normal workloads still running alongside (load around 2 before the test)
- Versions: Caddy v2.10.2, Nginx 1.24.0 from the Ubuntu repositories
- Load generator:
wrkwith 4 threads, 100 concurrent connections, 10 seconds per run, three rounds per scenario - Scenario 1, static: a 27 KB text file served directly by the web server
- Scenario 2, reverse proxy: a tiny Node.js app that returns a JSON object
- Everything over loopback, no TLS, both servers running at the same time on their own ports
- Nginx with
worker_processes auto(12 workers),sendfile onand an upstream withkeepalive 64, i.e. the way you would sensibly set up a proxy - Caddy with default settings, no tuning at all
The Node app on its own, with no proxy in front, handled 38,341 requests per second. That is the reference point for scenario 2.
The results
| Scenario | Caddy (req/s) | Nginx (req/s) | Ratio |
|---|---|---|---|
| Static file, run 1 | 67,367 | 141,657 | 2.1× |
| Static file, run 2 | 68,430 | 140,578 | 2.1× |
| Static file, run 3 | 74,312 | 144,017 | 1.9× |
| Reverse proxy, run 1 | 33,582 | 43,441 | 1.3× |
| Reverse proxy, run 2 | 33,778 | 44,431 | 1.3× |
| Reverse proxy, run 3 | 33,670 | 43,720 | 1.3× |
The rounds sit close together, so the results are not a fluke. Latency tells the same story: static files averaged about 0.5 ms on Nginx versus 1.6 ms on Caddy; as a proxy, 2.4 ms versus 4 to 5 ms.

What the numbers mean
For static files, Nginx is roughly twice as fast. That is not a small lead, that is a different league. Nginx is built for exactly this, and twenty years of optimisation in C show.
As a reverse proxy, the gap shrinks to about 30 percent. Here the request spends most of its time in the application anyway; the proxy is just part of the path. Notable and honestly surprising: Nginx as a proxy was faster than hitting the app directly (about 44,000 vs 38,000). Our guess: Nginx funnels the 100 client connections into at most 64 persistent connections to the backend, and the single-threaded Node app copes better with fewer concurrent connections. We could not prove that in this test, so it stays a guess.
In an extra round we also measured how much CPU time the servers used. That is often the more important number, because it decides how much of the server is left for your actual application:
| Per request (approx.) | Caddy | Nginx |
|---|---|---|
| Static file | ~98 µs CPU | ~35 µs CPU |
| Reverse proxy | ~200 µs CPU | ~56 µs CPU |
These are rough, because ps reports CPU time only to the second, but the order of magnitude is clear: Nginx needs roughly a third to a quarter of the CPU for the same work.
Memory
After the load tests the test Caddy used about 56 MB of memory (PSS). All 13 Nginx processes together, master plus 12 workers, came to about 29 MB. Our production Caddy with 139 sites and nearly a thousand certificate files sits at about 100 MB. On a server with 4 GB of RAM neither matters. On a tiny VPS with 512 MB or 1 GB, the 50 to 70 MB difference would be noticeable but not decisive. How to estimate your server’s overall memory needs is covered in How Much RAM Does a Server Need?.
What we deliberately do not claim
- No TLS in the test. In real life, encryption comes on top. It costs both servers CPU time and probably narrows the relative gap, but we could not measure that cleanly here.
- Load generator and servers on the same machine.
wrkshared the CPU with the servers. Absolute numbers are therefore lower than on separate hardware; the ratios between Caddy and Nginx are comparable, because both ran under the same conditions. - Nginx 1.24 instead of the latest release. We used what Ubuntu ships, because that is what most readers will install.
- No tuning for Caddy. Nginx had a sensibly configured upstream connection, Caddy ran with defaults. That reflects how both are typically run, but it is not a like-for-like contest.
So what does it mean for your website?
The most important number is not the ratio but the absolute size. Caddy served more than 67,000 static files per second. That is around 5.8 billion requests a day, on part of a shared VPS. A normal company website, blog or small shop gets maybe a few requests per second, a few hundred at peak. The web server is practically never the bottleneck there; the database, PHP or the application behind it is.
In other words: the speed difference is real, but irrelevant for the vast majority of projects. It becomes relevant if you run a CDN-like setup with huge numbers of static files, if you push thousands of requests per second through a proxy, or if server costs at high traffic are a real line in your budget.
Cloud Codes walks through switching from Nginx with Certbot to Caddy from a developer’s point of view. We share the enthusiasm; on performance, our measurements paint a more nuanced picture.
Security: Where Caddy Has Better Defaults
During the benchmark we also checked which headers actually reach the backend. We deliberately sent a forged header, X-Forwarded-For: 6.6.6.6, as if an attacker were claiming to come from that address.
With Caddy, the backend received "xff": "::1", the client’s real address. Caddy replaced the forged value. Caddy also adds a Via: 1.1 Caddy header.
With Nginx using the most-copied line on the internet, proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;, the backend received "xff": "6.6.6.6, 127.0.0.1". Nginx kept the forged value and merely appended the real address.
That is not an Nginx bug. The behaviour is documented and intended for proxy chains. It gets dangerous when your application simply takes the first entry in the list as the client IP. Then any visitor can claim to come from any address and, for example, bypass an IP-based rate limit or an IP ban. With Nginx you have to solve this deliberately with proxy_set_header X-Forwarded-For $remote_addr; or the realip module. With Caddy the safe option is the default; trusted proxies in front of it (a CDN, for example) are added explicitly with trusted_proxies.
A second, smaller difference: Caddy passed the Host header through unchanged including the port (localhost:18082), while Nginx with $host dropped the port (localhost). If your application builds absolute URLs from the Host header, that can be the difference between working and broken links.
More security points:
- Memory safety: Caddy is written in Go, which practically rules out whole classes of bugs such as buffer overflows. Nginx is written in C and has a very good security track record, but with the risks C inherently brings.
- TLS defaults: Caddy only uses modern protocols and ciphers without you configuring anything. With Nginx, TLS quality depends on which template you copied.
- Certificates do not expire: Sounds trivial, but it is one of the most common causes of outages. Caddy renews about 30 days before expiry and automatically retries on errors.
- Admin API: Caddy’s admin API listens only on localhost by default. That is fine, but you should know it exists. In our benchmark we turned it off with
admin off.
Neither replaces a firewall. A reverse proxy reduces the number of open doors, but it does not lock the others. How to set up a server properly from the start is covered in Linux Server Setup.
A Pitfall From the Test: Who Actually Reads Your Files?
On the first attempt Nginx did not serve our test file at all. It answered with 403 Forbidden. Caddy served the same file from the same folder without complaint. The error log said: open() "/tmp/bench/www/test.txt" failed (13: Permission denied).
The cause was instructive. We had started both servers as root. The Caddy process then ran entirely as root. Nginx, however, only runs the master process as root and runs the workers, which actually read the files, as an unprivileged user; without a user directive that is nobody. Our /tmp directory on that server is readable only by root, so nobody was not allowed to open the file.
Two lessons:
- A 403 from Nginx is almost always a permissions problem, not a configuration error. Use
namei -l /path/to/fileto check whether the worker user can enter every directory along the way. - Nginx’s privilege separation is a security feature. You should not run Caddy as root either. The official packages create a dedicated
caddyuser for it, and that is how our production server runs. Our test Caddy as root was convenient but wrong for production.
PHP, WordPress and Shopware: Caddy vs Nginx for Classic Applications
Many readers do not want to run a Node app but WordPress, Shopware, Nextcloud or Laravel. Both servers talk to PHP via PHP-FPM. In Caddy it is one line:
shop.example.com {
root * /var/www/shop/public
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
encode zstd gzip
}
php_fastcgi already includes the typical rewrite rules for frameworks with a central index.php. In Nginx you write a location ~ \.php$ block with fastcgi_pass, fastcgi_param SCRIPT_FILENAME and a try_files rule. It is well documented, but also a classic source of mistakes.
To be fair: for Shopware, Magento and many large PHP applications, vendors ship Nginx example configurations, rarely Caddy ones. Ask for help in a vendor forum and you will almost always be asked for your Nginx config. That is a real reason to stay with Nginx for such applications, especially when several people look after the server. That is why we use Nginx for our Shopware work and Caddy for our own tools.
Docker: Caddy vs Nginx in Containers
Both have official Docker images and both work well in front of containers. The difference, again, is effort:
- Caddy needs a volume for
/datain the container so certificates survive a restart. Forget it and Caddy fetches new certificates on every restart and eventually hits Let’s Encrypt’s rate limits. That is by far the most common Caddy pitfall in Docker setups. - Nginx needs an extra path for certificates in the container, usually a Certbot container or a volume with certificates from the host. The popular Nginx Proxy Manager solves exactly that with a web UI.
If your containers come and go constantly, look at Traefik, which discovers services from Docker labels. For a few fixed services in a Compose file, Caddy is usually the least hassle. A complete multi-service example is in our Docker Compose example article.
Modules and Extensions
Nginx has the larger ecosystem: Lua via OpenResty, ModSecurity as a web application firewall, Brotli, GeoIP, RTMP for streaming and many more. Many are dynamic modules you can load; some must be compiled in.
Caddy is extended with plugins compiled into the binary using the xcaddy tool. Popular ones include DNS provider modules (for wildcard certificates via the DNS challenge), rate limiting, Coraza as a WAF, and layer-4 proxying. The download page on the Caddy website will build a binary with your chosen plugins. The catch: plugins mean a custom binary you have to keep updated yourself, instead of just taking the package from the repositories.
A Caddy feature with no real Nginx counterpart is On-Demand TLS: Caddy obtains a certificate the moment the first request for an unknown domain arrives, after asking your application whether that domain is allowed. For SaaS platforms where customers connect their own domains, that saves an enormous amount of infrastructure.
Day-to-Day Operations: Logs, Reloads, Troubleshooting
A few things you only notice after a few months:
- Reloads: Both reload configuration changes without dropping connections (
systemctl reload caddyornginx -s reload). Caddy fully validates the new config and keeps the old one on errors. With Nginx, always runnginx -tfirst. - Formatting:
caddy fmt --overwriteformats the Caddyfile consistently. At 1,000 lines that is gold. - Logs: Nginx writes classic text logs every tool understands. Caddy writes structured JSON logs by default. They are excellent for machine analysis but more tiring to read by eye. We analyse our Caddy logs, for example, to see which AI crawlers visit us.
- Troubleshooting: For almost every Nginx error message there is an answer online. For Caddy you will often find a good answer in the official forum, but less often five different ones on Stack Overflow.
- One big file or many small ones: Caddy supports
importto split the Caddyfile. We use it exactly once, for a separately maintained domain list; everything else lives in one big file out of convenience. With 139 sites that is borderline, but with a good editor search it is surprisingly manageable.
Migrating From Nginx to Caddy
If you want to switch, go step by step:
- Test Caddy on other ports. Run Caddy alongside Nginx, for example on port 8080, and check every site with
curl -H "Host: your-domain.com" http://127.0.0.1:8080/. - Translate site by site. Most
serverblocks shrink to three to ten lines. Rewrite rules (rewrite,try_files) need the most attention. - Think about certificates. When you switch, Caddy fetches a new certificate for every domain. With very many domains at once you can hit the certificate authority’s rate limits, so move in stages.
- Check headers. As shown above, Caddy and Nginx pass
HostandX-Forwarded-Fordifferently. Test logins, redirects and anything that uses the client IP. - Swap the ports. Stop Nginx, let Caddy listen on 80 and 443, but keep Nginx installed for a few days so you can switch back quickly.
The reverse, from Caddy to Nginx, works just as well. The main effort then is certificate management, which you have to set up yourself.

Caddy vs Nginx: Decision Guide by Scenario
| Your scenario | Our recommendation | Why |
|---|---|---|
| Homelab, self-hosting, a few services | Caddy | HTTPS with no effort, short config |
| Many subdomains on one server | Caddy | Each new site takes minutes |
| SaaS with customer domains | Caddy | On-Demand TLS saves your own certificate logic |
| Lots of static traffic | Nginx | Roughly twice as fast, less CPU |
| Shopware, Magento, large PHP app | Nginx | Official example configs, support |
| Team already knows Nginx | Nginx | Knowledge beats three saved lines |
| Kubernetes, constantly changing containers | Traefik (or Nginx Ingress) | Automatic service discovery |
| You want to learn web servers | Nginx | Industry standard, in demand everywhere |
If you are only just renting a server and are unsure which kind, our VPS vs Dedicated Server comparison helps. For a deep dive into Nginx as a proxy, see our complete Nginx reverse proxy guide.
Conclusion: Caddy vs Nginx
Our benchmark showed what many suspect but rarely prove cleanly: Nginx is faster. About twice as fast for static files, around 30 percent as a reverse proxy, and with a third to a quarter of the CPU time and roughly half the memory.
And yet we run our own server on Caddy. Because our bottleneck was never the web server, it was our time. 85 certificates a month that nobody has to look after, 139 sites in a file you can still read months later, and defaults that discard forged IP addresses without anyone having to remember to: for us that is worth more than performance headroom we never use.
So the honest rule is: choose Caddy when your time is scarcer than your CPU. Choose Nginx when it is the other way round, or when the ecosystem demands it. Both are mature, both are free, and if you choose wrong, switching takes an afternoon.
Frequently Asked Questions About Caddy vs Nginx
Is Caddy faster than Nginx?
No. In our benchmark Nginx was about twice as fast for static files (roughly 142,000 vs 70,000 requests per second) and about 30 percent faster as a reverse proxy. For most websites it still does not matter, because both handle far more requests than a normal site will ever see.
Is Caddy production-ready?
Yes. Caddy runs in production at many companies, and this website is served by Caddy. At the time of writing our production process had been running for 28 days without a restart, serving 139 sites.
Does Caddy need Certbot or a separate Let’s Encrypt setup?
No. Caddy gets certificates from Let’s Encrypt or ZeroSSL by itself as soon as you put a domain name in the configuration, and renews them automatically. The only requirements are that the domain points to your server and ports 80 and 443 are reachable.
Can Caddy fully replace Nginx?
For most use cases, yes: static files, reverse proxy, load balancing, PHP, WebSockets, compression. The limits are very specialised Nginx modules (such as Lua via OpenResty or RTMP streaming) and applications whose vendor only supports Nginx configurations.
How much memory does Caddy use compared to Nginx?
In our test, after load, about 56 MB for Caddy versus about 29 MB for all Nginx processes combined. Our production Caddy with 139 sites uses about 100 MB. On servers with 1 GB of RAM or more, the difference is practically irrelevant.
Is Caddy more secure than Nginx?
Caddy has safer defaults: automatic HTTPS, modern TLS settings, and forged X-Forwarded-For headers are replaced by default rather than passed on. Go is also memory-safe. Nginx can be run just as securely, but you have to configure it correctly yourself.
Does Nginx support HTTP/3?
Yes, officially since version 1.25. Many Linux distributions still ship older versions, Ubuntu for example ships 1.24, and HTTP/3 must additionally be enabled per site. In Caddy, HTTP/3 is on by default.
What about Nginx Proxy Manager?
Nginx Proxy Manager is a web UI that combines Nginx with automatic Let’s Encrypt certificates. It is especially popular in homelabs because you do not have to write configuration files. If you prefer working with a text file, Caddy gets you the same result with fewer moving parts.
Caddy vs Nginx vs Traefik: which for Docker?
For a few fixed containers in a Compose file, Caddy is the simplest. If containers start and stop automatically all the time, Traefik with its label-based service discovery is the better choice. Nginx in a container is mainly worth it when you need its performance or its modules.
Should a beginner start with Caddy or Nginx?
If you want something working quickly: Caddy. If you want to learn web servers professionally: Nginx, because job ads, hosts and documentation assume it almost everywhere. Ideally, learn Nginx properly and use Caddy where it saves you work.
Why does Nginx return 403 even though the file exists?
Almost always a permissions problem. Nginx workers run as an unprivileged user (www-data on Ubuntu) and must be allowed to enter every directory in the path. That is exactly what happened to us in the benchmark. namei -l /path/to/file shows you immediately which directory is blocking.
