Caddy Web Server Review: Automatic HTTPS Without the Ceremony
A detailed look at Caddy's automatic TLS, Caddyfile syntax, and reverse proxy setup — and where it falls short compared to Nginx.
If you’ve ever spent an afternoon wrestling with Certbot cron jobs, nginx reload scripts, and ACME challenge directories, you already understand the problem Caddy is solving. The pitch is simple: point it at a domain, and it handles certificate provisioning, renewal, OCSP stapling, and HTTP-to-HTTPS redirection with zero additional configuration. No Certbot. No systemd timers. No manual reloads after renewal.
Caddy (v2.11.3 as of May 2026, Apache 2.0 licensed) is written in Go and has accumulated over 72,000 stars on GitHub. That’s not a niche tool. The question isn’t whether Caddy works — it clearly does — but whether its design tradeoffs make sense for your specific deployment context.
How Automatic HTTPS Actually Works
Caddy’s automatic HTTPS is powered by a built-in ACME client. When you start Caddy with a public domain name in your config, it reaches out to Let’s Encrypt or ZeroSSL, completes an ACME challenge, stores the certificate, and starts serving HTTPS. Renewal happens in the background before expiry without a restart.
Three ACME challenge methods are available:
- HTTP-01: Caddy serves a challenge file on port 80. Straightforward, but requires port 80 to be reachable from the internet.
- TLS-ALPN-01: The challenge runs over port 443 using a special TLS handshake. No port 80 dependency, but port 443 must be open.
- DNS-01: Caddy writes a TXT record to your DNS zone. This is the only method that works for wildcard certificates, and it requires DNS provider credentials (Caddy has plugins for most major providers). Neither port needs to be open, which makes it viable behind a firewall.
Both HTTP-01 and TLS-ALPN-01 are enabled by default. DNS-01 requires explicit configuration and a provider plugin.
For internal services — localhost, private IPs, or .local names — Caddy spins up its own local CA and issues self-signed certificates, then tries to install that CA into your system’s trust store automatically. This works well on Linux and macOS. Whether it will work in your CI containers or Docker images depends on your setup, and you may need to handle trust store installation manually.
Caddy also supports on-demand TLS: certificates are obtained during the first TLS handshake for a domain, rather than at startup. This is useful if you’re proxying thousands of customer subdomains and don’t know them all at configuration time — think multi-tenant SaaS platforms. The catch is that on-demand TLS must be paired with an “ask” endpoint, a URL Caddy calls to verify that a given domain is authorized before it requests a certificate. Without this restriction, a misconfigured server could be tricked into requesting certificates for arbitrary domains, burning through ACME rate limits or triggering abuse detection. The documentation is explicit about this requirement.
The Caddyfile: What Simple Configuration Looks Like in Practice
The Caddyfile format is Caddy’s user-facing configuration language. Here’s what a production-ish setup for a Node.js API and a static frontend looks like:
# Static frontendapp.example.com { root /var/www/frontend encode gzip try_files {path} /index.html file_server}
# API reverse proxyapi.example.com { reverse_proxy localhost:3000}That’s it. No server {} blocks, no listen directives, no SSL certificate paths. Caddy reads the domain names, recognizes they’re public hostnames, and handles the rest. HTTP-to-HTTPS redirects are automatic — you don’t write them.
Compare that to a typical Nginx config that does the same thing: two server blocks for HTTP and HTTPS per domain, a Certbot configuration, a cron entry for renewal, and a post-renewal hook to reload nginx. The operational surface is genuinely smaller with Caddy.
For path-based routing to multiple backends:
example.com { reverse_proxy /api/* localhost:5000 root /srv/public file_server}Caddy evaluates directives in a defined order, so the reverse_proxy matcher intercepts /api/* requests before file_server sees them.
The Caddyfile isn’t the only configuration interface. Caddy also exposes a JSON API on port 2019 (by default), which accepts configuration changes without a restart. This matters if you’re building tooling around Caddy or need programmatic config updates — CI pipelines, orchestrators, or custom control planes can push updates via HTTP rather than templating config files. The JSON config is more verbose than the Caddyfile but is fully documented and can be reloaded live.
Where Caddy Falls Short
Caddy’s defaults are reasonable, but the ecosystem of third-party modules is narrower than Nginx’s. If your architecture depends on specific Nginx modules — video streaming modules, LDAP authentication, or certain WAF integrations — you’ll need to verify that a Caddy equivalent exists. Caddy’s module system allows compiling custom binaries with additional plugins, but that adds a build step and complicates upgrade paths. The official xcaddy tool manages this, but it’s another thing to maintain.
On raw throughput, Nginx still holds an advantage for large-file streaming. One independent benchmark (Tyblog, not sponsored by either project) showed Caddy slightly ahead for small-file workloads while Nginx retained an edge for large static assets. For typical API proxying or serving web apps, the difference is unlikely to matter — but if you’re running a high-traffic CDN origin or large media server, test your specific workload rather than assuming parity.
Caddy’s CVE history is shorter than Nginx’s — the Go runtime eliminates an entire class of memory-safety bugs by construction — but “fewer historical CVEs” isn’t the same as “more secure.” Evaluate based on your threat model and your team’s familiarity with Go-based operational tooling.
Certificate storage is another consideration. Caddy writes certificates to the local filesystem (defaulting to the user’s home directory) or to a configured storage backend. If you run Caddy in a container or ephemeral VM, you need persistent storage mounted and writable, or certificates will be re-requested on every restart — which will eventually hit ACME rate limits. Multiple Caddy instances pointed at the same storage backend will coordinate automatically, which simplifies horizontal scaling, but the storage backend itself becomes a dependency to manage.
When to Choose Caddy Over Nginx
The honest answer is: Caddy is a better default for new deployments where TLS management complexity is a friction point and you don’t have deep Nginx expertise already. If your team knows Nginx, has existing configurations, and has working Certbot automation, migrating to Caddy has a real cost in exchange for a benefit that may be marginal in your case.
Caddy earns its place for:
- Solo developers and small teams who want HTTPS without maintaining certificate renewal infrastructure.
- Internal tooling where you want TLS on private services without manually managing a CA.
- Multi-tenant platforms where on-demand TLS handles dynamic customer domains.
- Docker-heavy setups where Caddy’s single binary and JSON API fit cleanly into a container orchestration model.
Nginx remains the better choice when you need specific ecosystem modules, are running extremely high-volume static file serving, or have a team with deep existing Nginx operational knowledge. Both servers are production-grade. The choice is about operational fit, not technical merit.
FAQ
Does Caddy work if my server is behind a load balancer that terminates TLS? +
Can I use Caddy with DNS providers like Cloudflare or Route 53? +
What happens to my certificates if I stop the Caddy process? +
Related tools
Beehiiv
Newsletter platform with built-in ad network and Boost referrals.
Try Beehiiv →
Webflow
Visual site builder with real CSS export and a CMS that scales.
Try Webflow →
Some links above are affiliate links. We may earn a commission if you sign up. See our disclosure for details.
Related reading
2026-05-21
AI-Powered Observability: Querying Telemetry in Plain English
Observability platforms now let you ask questions of logs, metrics, and traces in natural language. Here's how the translation layer works, what it genuinely helps with, and where it breaks.
2026-05-21
Mac Mini as AI Agent Infrastructure: Why Apple Silicon Powers Local LLM Inference
How Apple Silicon's unified memory architecture makes the Mac Mini a practical local inference node — benchmarks, real costs, setup with Ollama and MLX, and honest tradeoffs versus cloud GPUs.
2026-05-21
NixOS & nixpkgs in 2026: Reproducible Dev Environments Without Docker
How Nix flakes and devShells replace Docker for local dev: what works, where it hurts, and whether the learning curve is worth it for your team.
2026-05-21
The Rust Sidecar Pattern: Fixing Python AI's Deployment Weakness
Python dominates ML development but struggles in production serving. The Rust sidecar pattern splits responsibilities: Python handles models, Rust owns the hot path. Here's the mechanics.
2026-05-21
SendGrid vs Mailgun vs Resend: Honest 2026 Email API Comparison
A grounded comparison of SendGrid, Mailgun, and Resend across pricing, developer experience, deliverability, and fit — for developers picking a transactional email API in 2026.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.