Nginx vs Caddy for Serving Static Sites

Self-hosting a static site comes down to choosing a web server, and the two serious choices today are Nginx — the long-standing default, fast and everywhere — and Caddy, which made automatic HTTPS its defining feature and has a much shorter configuration format. Both serve static files very well. The differences that matter are in raw performance under heavy load, memory use, how certificates are handled, how many lines a correct configuration takes, and which mistakes each one makes easy.

This comparison serves the same 2,400-page Hugo documentation site from Nginx 1.26 and Caddy 2.8 on identical 2-vCPU, 4 GB VMs, with equivalent configurations: HTTPS, HTTP/2 and HTTP/3, precompressed Brotli and gzip, caching and security headers, a custom 404. It measures throughput and latency with load tests, memory, configuration size and setup time. It belongs to Self-Hosting Static Sites on S3, Nginx and Caddy; the individual setups are in Serving a Static Site with Nginx and Serving a Static Site With Caddy and Automatic HTTPS.

Method

  • Servers: two identical cloud VMs, 2 vCPU, 4 GB, Ubuntu 24.04, same region.
  • Load: oha from a third VM in the same region, 60-second runs at increasing concurrency, HTTPS with HTTP/2, keep-alive on, Accept-Encoding: br.
  • Files: a 12 KB (Brotli) HTML page, a 48 KB CSS file and a 404 response.
  • Repeated three times, median reported.

Throughput and Latency

Measure (HTML page, 200 concurrent)Nginx 1.26Caddy 2.8
Requests per second21,40018,100
Latency p508.9 ms10.6 ms
Latency p9924 ms37 ms
CPU at saturation2 cores, 100%2 cores, 100%
Throughput of Nginx and Caddy serving a static HTML page at increasing concurrency Paired bars of requests per second. At 10 concurrent connections, Nginx 9,800 and Caddy 9,100. At 50, Nginx 18,600 and Caddy 16,200. At 200, Nginx 21,400 and Caddy 18,100. At 1,000, Nginx 20,900 and Caddy 17,300. Requests per second by concurrency Nginx 1.26 Caddy 2.8 10 concurrent 9800 rps 9100 rps 50 concurrent 18600 rps 16200 rps 200 concurrent 21400 rps 18100 rps 1,000 concurrent 20900 rps 17300 rps
oha load tests over HTTPS with HTTP/2 from a VM in the same region, median of three 60-second runs.

Nginx was about 18% faster at saturation and had a tighter tail. Both saturated the CPU first; neither hit memory or file descriptor limits. For context, 18,000 requests per second is about 1.5 billion requests a day — far beyond what a single-origin static site sees, especially with a CDN in front, where the origin may serve a few requests per second.

Memory

MeasureNginxCaddy
Resident memory, idle9 MB (master + 2 workers)38 MB
Resident memory, 1,000 concurrent14 MB70 MB

Caddy is a single Go binary with its certificate management, admin API and module system loaded; Nginx's C workers are leaner. On a 4 GB machine neither matters. On a 512 MB VM running several services, Nginx leaves more room.

TLS and Certificates

This is Caddy's defining difference. With a domain name in the Caddyfile, Caddy obtains a certificate, renews it, staples OCSP and redirects HTTP to HTTPS without any further configuration. Nginx needs certbot (or acme.sh), a renewal timer and a reload hook, plus TLS settings in the config.

TaskNginxCaddy
Get a certificatecertbot + nginx pluginAutomatic
Renewalsystemd timer + reload hookAutomatic
HTTP → HTTPS redirectSeparate server blockAutomatic
Modern TLS defaultsCopy a recommended configDefault
HTTP/3listen 443 quic + add_header Alt-SvcOn by default

On the test servers, setting up HTTPS took 25 minutes on Nginx (including certbot and testing renewal with --dry-run) and none on Caddy.

Configuration Size

Equivalent configurations, including headers, compression, caching, 404 and redirects:

Configuration lines needed for an equivalent static site setup Vertical bars. The Nginx configuration took 74 lines plus a 12-line certbot renewal setup. The Caddy configuration took 27 lines with nothing else needed. Lines of configuration for the same behaviour 74 Nginx config 86 Nginx + certbot setup 27 Caddyfile
Counted without comments or blank lines; both configurations serve the same headers, compression, caching and redirects.

The Caddy configuration for the site:

docs.example.com {
    root * /srv/docs/current
    encode zstd gzip
    file_server { precompressed br gzip }
    import security_headers
    @assets path /css/* /js/* /fonts/* /images/*
    header @assets Cache-Control "public, max-age=31536000, immutable"
    header Cache-Control "public, max-age=0, must-revalidate"
    handle_errors { rewrite * /404.html; file_server }
    redir /old-guide/ /guides/new/ permanent
}

The Mistakes Each Invites

An audit of 30 self-hosted static site configurations from the team's other projects and public repositories found these issues:

MistakeFound in Nginx configs (of 18)Found in Caddy configs (of 12)
Security headers missing on some paths9 (add_header inheritance)0
SPA fallback serving 200 for missing pages6 (try_files … /index.html)2 (try_files {path} /index.html)
Weak or outdated TLS settings40
No compression for some types5 (gzip_types incomplete)1
Certificate renewal not reloading server20

Nginx's inheritance rule for add_header — a location with any add_header drops all inherited ones — accounted for the most common problem, as described in Cache-Control Headers for Static Sites in Nginx. The SPA-style fallback appeared in both, copied from tutorials; it causes the soft 404s covered in Fixing Soft 404s on Static Hosts.

Operations Over Time

Running both for three months on the test servers surfaced day-to-day differences beyond the benchmarks. Reloads: both reload configuration without dropping connections (nginx -s reload, caddy reload); Caddy's reload validates the new config first and refuses a broken one, while Nginx needs nginx -t run beforehand — easy to forget in a hurried change. Logs: Nginx writes the familiar combined log format that every log tool parses; Caddy writes structured JSON by default, which is better for querying but needed a parser update in the team's log pipeline. Upgrades: Nginx came from the distribution's packages with security updates applied automatically; Caddy came from its own APT repository, which also updated automatically once configured. Observability: Caddy exposes Prometheus metrics through its admin endpoint with one setting; Nginx's stub_status gives basic counters, and richer metrics need an exporter or the commercial version.

Neither server needed attention for anything other than configuration changes during the three months. The one incident was on Nginx: a certificate renewed by certbot but the renewal hook that reloads Nginx had been lost in a server rebuild, and browsers began warning six days later when the old certificate expired. Caddy cannot fail in that particular way, because it manages and loads its certificates itself. Monitoring TLS Certificate Expiry covers the alert that should have caught it earlier.

Behind a CDN

Most production static sites put a CDN in front of the origin, which changes the comparison. The CDN terminates TLS for readers and caches nearly everything, so the origin serves revalidations and cache misses — on the documentation site, about 4 requests per second at peak. At that load, throughput and memory differences vanish, and the origin's certificate only needs to satisfy the CDN. What remains is configuration correctness and maintenance, where Caddy's defaults help most.

Verdict

Decision tree for choosing Nginx or Caddy to serve a static site The root asks about the deployment. A single site or a few sites on one server, managed by a small team, points to Caddy for automatic HTTPS and short config. Very high traffic served directly without a CDN points to Nginx for throughput. An existing Nginx fleet with established tooling and certificates points to staying on Nginx. A very small machine with tight memory points to Nginx. Which server for a static site? What is the deployment like? Few sites, small team Caddy auto HTTPS, short config Huge traffic, no CDN Nginx ~18% more throughput Existing Nginx fleet Nginx keep the tooling Tiny machine Nginx lower memory
Behind a CDN, the performance difference is irrelevant and simplicity decides.
  • Caddy for most self-hosted static sites: automatic HTTPS, a third of the configuration, and far fewer ways to get headers or TLS wrong. Its lower peak throughput is irrelevant at realistic traffic, especially behind a CDN.
  • Nginx for very high traffic served directly, very small machines, or teams with established Nginx tooling — with a configuration review for the add_header trap.

Pitfalls

  • Choosing on benchmarks alone. Both are far faster than a static site's origin usually needs.
  • Copying SPA configs. Both servers make the soft-404 fallback easy to add by accident.
  • Forgetting renewal reloads on Nginx. Certificates renew but the server keeps the old one.
  • Assuming Caddy needs no headers. HTTPS is automatic; security headers are not.

Conclusion

Serving the same site on identical machines, Nginx delivered about 18% more requests per second with less memory, and Caddy delivered automatic HTTPS, a configuration a third the size, and none of the header and TLS mistakes common in Nginx audits. For a static site, especially one behind a CDN, Caddy's simplicity is usually worth more than Nginx's throughput.

FAQ

Is Nginx faster than Caddy for static files?

In the benchmark in this guide, Nginx served about 18 percent more requests per second than Caddy for small HTML files on the same 2-vCPU server, with slightly lower tail latency. For a typical static site behind a CDN, both are far faster than needed; the difference only matters for very high traffic served directly from one machine.

Why do people choose Caddy for static sites?

Mainly for automatic HTTPS. Caddy obtains and renews certificates from Let's Encrypt or ZeroSSL with no configuration, redirects HTTP to HTTPS, and enables modern TLS settings by default. Its configuration for a static site is a few lines. Nginx needs certbot or similar and more configuration for the same result.

Which uses less memory?

Nginx. In this test Nginx's workers used about 9 megabytes at idle and 14 under load, while Caddy used about 38 megabytes idle and 70 under load. Both are small on any modern server, but Nginx fits better on very small machines or when running many instances.

Which is less likely to be misconfigured?

Caddy has fewer traps for static sites: headers apply consistently, HTTPS is on by default, and compressed files are served with one directive. Nginx's add_header inheritance, try_files patterns copied from single-page apps and manual TLS settings cause most of the misconfigurations seen in audits of self-hosted static sites.

Filed underComparisonsnginxCaddy