
If you run anything on the internet โ a blog, a dashboard, an API, a home automation stack โ you eventually need something that terminates TLS, serves static files, and proxies requests to the right backend. For years that meant NGINX or Apache plus Certbot plus a renewal cron job plus a prayer. Caddy changed that. It is a web server and reverse proxy delivered as a single Go binary whose defining trait is
automatic HTTPS: point it at a domain, write a three-line config, and it obtains and renews TLS certificates for you with no external tooling.
Caddy carries roughly 75,000 GitHub stars and is licensed under Apache-2.0. Here is the part that separates it from almost every other serious infrastructure project we have covered:
every feature ships in the open, free build. There is no enterprise edition, no paid tier, no open-core gate. The automatic HTTPS, the HTTP/3, the live admin API, the load balancing, the security middleware โ all of it is in the Apache-licensed core. The commercial steward (Stack Holdings, via ZeroSSL) holds no feature lever over the software you run.
But Caddy has one operational quirk that surprises people: its plugins are
compiled into the binary with a tool called
xcaddy. You do not load a module at runtime; you rebuild Caddy with the module included. That is a different mental model from NGINX's dynamic modules or a config-toggle world, and it has real consequences for your CI/CD and for multi-instance certificate coordination. The rest of this article is an honest teardown of what Caddy delivers, where the sharp edges are, and precisely where your certificates and request logs go.
1. What Caddy Is
Caddy is a web server and reverse proxy written in Go. It serves static files, proxies HTTP and WebSocket traffic to backends, terminates TLS, redirects, rewrites, and applies middleware โ all configured by a short, human-readable
Caddyfile or a JSON document. It compiles to a single statically linked binary with no runtime dependencies, which is why you can drop it on a bare VPS and be done.
The project is led by its original creator, Matt Holt, with a broad community of contributors, and is backed commercially by Stack Holdings (the ZeroSSL people). It reports in the high-60-thousands to 75,000 stars and is one of the most-deployed pieces of self-hosting infrastructure โ it sits in front of everything from Nextcloud instances to home automation dashboards. The current stable line is 2.x, with 2.11.4 released in June 2026 as a security-focused patch.
Caddy's licence is Apache-2.0. That permissive licence is worth stating plainly because it is rare at this tier: you can use Caddy commercially, modify it, and embed it in a product with no obligation beyond attribution and notice. The company behind it makes money on related services (certificate management, hosting), not by locking features behind a paywall. For a self-hoster that means the binary you download is the whole product.
2. Automatic HTTPS: The Feature That Defined the Project
The headline is automatic HTTPS, and it is not marketing. On first run, Caddy obtains a certificate from a public ACME certificate authority (Let's Encrypt by default, ZeroSSL as fallback), installs it, and from then on renews it automatically before expiry. It also enables HTTP/2 and HTTP/3, redirects port 80 to 443, and staples OCSP โ all without a single extra command. The
init() function in the binary agrees to the ACME terms of service non-interactively, so headless and automated deployments work without a human clicking through a prompt.
This removes the two chores that make manual TLS fragile: the initial issuance and the renewal. No Certbot, no acme.sh, no cron job that breaks silently when the hook script has a bug. For a self-hoster managing a handful of services, automatic HTTPS is the single biggest quality-of-life improvement in a decade of web serving. It is also why Caddy became the default reverse proxy recommendation across the self-hosting community.
The mechanism under the hood is CertMagic, a separate library maintained by the same team that handles ACME negotiation, storage, renewal scheduling, and OCSP stapling. Understanding that CertMagic is doing the work helps explain the later sections about storage backends and coordination: certificate management is a first-class, stateful subsystem, not a one-time setup.
3. The Caddyfile vs JSON
Caddy has two configuration formats. The
Caddyfile is a concise, readable declarative language โ
reverse_proxy localhost:3000,
file_server,
tls internal โ that most people use for day-to-day config. The
JSON document is the native config that the Caddyfile is adapted into; it is what the live admin API speaks and what GitOps automation should target.
The guidance is consistent across the ecosystem: use the Caddyfile when a human is writing and reading the config; use JSON when a machine is driving updates, diffing, or applying controller-managed changes. You can convert between them (
caddy adapt), and config adapters translate other formats into JSON. For automation, JSON plus the REST admin API is the correct surface, because it lets you change routes without restarting the process โ a capability NGINX simply does not have in the same form.
4. The Live Admin API
Caddy exposes a live admin API (on a local endpoint by default) that lets you reload config, inspect the running state, and change routes without a process restart. This is a genuine differentiator: NGINX requires a config test and reload (or a full restart in some setups); Caddy applies config changes in place through the API. For dynamic environments โ service discovery, ephemeral routes, controller-driven ingress โ the admin API is the feature that makes Caddy programmable rather than just configurable.
The security note is mandatory: the admin endpoint must never be exposed to the network. Bind it to localhost, or disable it, or protect it behind strict access controls. An internet-exposed admin API is remote code execution by another name, because it can load new config and new handlers. The default binds to localhost, which is safe; the danger appears only when someone "opens it up for convenience." Don't.
5. HTTP/3, ECH, and PQC: The Protocol Leadership
Caddy has consistently been first among major web servers to ship modern transport security. HTTP/3 (QUIC) is a first-class feature โ no patched OpenSSL, no ngtcp2 build, just on by default โ which requires exposing port 443/UDP for QUIC negotiation. Caddy was also an early adopter of
Encrypted ClientHello (ECH), which encrypts the SNI portion of the TLS handshake and hides the destination hostname from passive observers, and of
post-quantum cryptography key exchange (x25519mlkem768) in the 2.10 line.
These are not party tricks; they are meaningful privacy and future-proofing features. ECH resists SNI-based censorship and traffic analysis; PQC resists harvest-now-decrypt-later threats. For a privacy-conscious operator, running a server that leads on these standards is a concrete advantage. The cost is the UDP port and the occasional client that mishandles HTTP/3 โ both manageable, both worth it.
6. The Plugin Model and xcaddy: The Rebuild Quirk
Here is the operational quirk every Caddy user must internalize. Caddy's features are
modules registered at compile time through Go interfaces. The standard build bundles the common ones. But if you need a module that is not in the standard set โ most often a
DNS provider plugin for ACME DNS challenges (needed when you cannot expose port 80 for HTTP challenges, e.g. behind a strict firewall or for wildcard certs) โ you do not load it at runtime. You
rebuild Caddy with xcaddy, passing the plugin package, and produce a new binary that includes it.
This means adding a capability is a build step, not a config toggle. For most users that is a one-time
xcaddy build with a Dockerfile or a CI job that pins the plugin and produces an image. The consequences: your deployment artifacts are now custom binaries, not stock packages; your CI/CD must own that build; and an upstream Caddy security release means rebuilding your image, not just pulling a new tag. Teams that treat Caddy like NGINX (pull the package, edit config) are surprised when their DNS plugin vanishes after an upgrade. Own the build.
The flip side is real strength: because everything is a typed Go module, Caddy's architecture is unusually clean and extensible, and third-party modules follow the same pattern as first-party ones. The rebuild model is a trade โ less runtime flexibility, more compile-time safety and a single coherent binary.
7. ACME Challenges: HTTP-01 vs DNS-01
Caddy obtains certificates via ACME, and the challenge type matters for your topology. The
HTTP-01 challenge is the default: the CA verifies you control the domain by hitting
http://yourdomain/.well-known/acme-challenge/..., which Caddy serves automatically. This requires port 80 reachable from the internet. The
DNS-01 challenge verifies by writing a TXT record to your DNS, which requires a DNS provider plugin (back to the xcaddy rebuild) and API credentials for your DNS host.
DNS-01 is the path when you cannot expose port 80 (strict egress firewalls, internal-only hosts) or when you need
wildcard certificates (
*.example.com), which HTTP-01 cannot issue. The decision is therefore architectural: stock Caddy handles the common case with zero extra work; wildcard or firewalled cases require the DNS plugin build and your DNS API keys in the config. Plan which you need before you deploy, because retrofitting DNS-01 means a rebuild and credential management you did not budget for.
8. Storage Backends and Multi-Instance Coordination
CertMagic stores certificates and ACME account data in a
storage backend. The default is the local filesystem. This is fine for a single Caddy instance. The moment you run
multiple Caddy instances โ two load-balanced reverse proxies, a active-active pair โ they must coordinate certificate management or they will race: each instance tries to obtain the same certificate, they trip ACME
rate limits, and issuance fails.
The fix is a
shared storage backend (Redis, Consul, S3, or a SQL database) that all instances use, so certificate state is coordinated and OCSP staples are shared. This turns storage choice into an architectural decision, not a default you accept. If you run more than one Caddy, you must plan the shared store up front; retrofitting it after a rate-limit outage is the painful way to learn it. Caddy's docs are clear on this; the failure mode is simply that people deploy instance two without thinking about instance one's cert state.
9. ACME Rate Limits: The Real Constraint
Let's name the constraint that bites multi-instance and lab-heavy deployments: public ACME authorities rate-limit certificate issuance per domain and per account. Issue too many certificates too fast โ common when you spin up and tear down test environments, or when uncoordinated instances all request the same cert โ and you get temporarily blocked, sometimes for a week. Caddy handles renewal well, but initial issuance at scale, or churn in CI, can trip limits.
Mitigations are straightforward but must be designed in: use the staging ACME endpoint for tests (Let's Encrypt operates a staging CA precisely for this), share storage across instances, and avoid tearing down and recreating certs in tight loops. For purely internal deployments, Caddy's
internal CA (
tls internal) issues certificates from a local CA with no public rate limit and no internet dependency โ the right choice for air-gapped or LAN-only services, and a feature NGINX reaches for
mkcert or
step-ca to approximate.
10. Caddy vs NGINX: The Honest Contrast
The comparison everyone wants is Caddy versus NGINX. NGINX is the incumbent: battle-tested, ubiquitous, scriptable with Lua, and the default in countless stacks. Caddy's advantages are automatic HTTPS, a far more readable config, HTTP/3 by default, a live admin API, and a single static binary with no dependency soup. For a self-hoster, Caddy is simply less work to operate securely.
NGINX's advantages are depth and ecosystem: more modules available at runtime, deeper L7 traffic-management features, massive institutional knowledge, and dominance in enterprise fleets. Caddy is a thin edge layer, not a service mesh โ it lacks deep traffic-splitting, full mesh policy, and service-discovery control planes. If your architecture needs those, Caddy is the edge component alongside other tools, not the whole story. The verdict: Caddy for the vast majority of self-hosted and mid-size deployments where "secure by default with minimal toil" wins; NGINX (or Traefik, which we covered) when you need its specific depth or are already standardized on it.
11. Caddy vs Traefik: The Kubernetes Question
We covered Traefik earlier in this series, and the Caddy-vs-Traefik question is real. Traefik's superpower is native Kubernetes and container integration: it discovers services dynamically from the orchestrator and configures routes automatically. Caddy can run on Kubernetes and has ingress support, but dynamic, service-discovery-driven ingress at cluster scale is Traefik's home ground. Caddy is happiest in front of a fixed set of sites and services โ a VPS, a homelab, a small fleet with mostly static routing.
The trade is operational model. Traefik's dynamism is powerful but its config can become opaque; Caddy's static-ish Caddyfile is easier to read and reason about for a known set of services. If you live in Kubernetes and want routes to appear as services appear, Traefik fits. If you run a defined set of services and prize a readable config plus automatic HTTPS, Caddy fits. Both are excellent; the right one follows your topology, not a feature checklist.
12. Security Posture and the 2026 CVE History
Caddy takes security seriously, and its 2026 release history shows it. The February 2026 release fixed six CVEs (a FastCGI RCE, a CSRF bypass, host and path matcher bypasses, a TLS auth fail-open, a glob sanitization bypass). The June 2026 v2.11.4 patched a Windows path bypass, a placeholder re-expansion flaw in query rewrites, a template XSS in the templates module, and header-field collisions with underscores. These are the normal churn of a public-facing server, and the project's rapid response is a point in its favor.
Two operator lessons fall out. First, keep Caddy patched and current โ a public-facing reverse proxy is exactly the component you cannot let rot. Second, the maintainers disclosed that more than 75% of incoming "security" reports were AI-generated spam or lazy incorrect submissions, and they started blocking the accounts responsible. That is a reminder that the infrastructure you depend on is maintained by finite human time; responsible disclosure and good behavior are not optional civic hygiene in open source.
13. The Single-Maintainer Bus Factor
The governance question worth stating plainly: Caddy's direction remains closely tied to its founder. The licence is permissive and no feature is paywalled, so the commercial steward holds no lever over the software you run โ that is the structural protection an Apache-2.0, no-open-core project gives you. But the bus factor, in the sense of "who decides what Caddy becomes," is narrower than a foundation-run project like Apache Superset.
For most self-hosters this is a non-issue: the binary you have works regardless of who leads next, and the source is Apache-licensed forever. For an organization standardizing Caddy across critical infrastructure, it is worth noting that "who steers the project" is one person plus a community, not a neutral foundation. Mitigate by vendoring the version you depend on and owning your xcaddy build, so a future direction change cannot force your hand. You are not at the licence's mercy; you are at the roadmap's, and the roadmap is one person's.
14. Observability: Prometheus and OpenTelemetry Built In
Caddy ships serious observability without extra config: a Prometheus metrics endpoint and OpenTelemetry traces and metrics are available in a default build. For a self-hoster who already runs a metrics stack, scraping Caddy is a one-liner. This is part of a broader theme โ Caddy's dependency choices (automemlimit and automaxprocs to respect cgroup limits, a bundled root certificate store so it does not depend on the OS cert bundle) show a project that thinks about container and production reality, not just the happy path.
The practical upshot: you can watch request rates, TLS handshake latencies, and upstream proxy errors from day one, which turns "the proxy feels slow" into a measurable signal. Pair it with the observability tool we covered and you have a complete picture of the edge.
15. Resource Footprint and the Single Binary
Caddy is light. A single static binary, modest RAM (tens of megabytes for typical loads), and near-zero CPU when idle, plus the cgroup-awareness mentioned above so it behaves correctly inside Docker and Kubernetes without manual tuning. Compared to a stack of NGINX plus Certbot plus a renewal cron plus a metrics exporter, Caddy is dramatically less to operate. This is the quiet reason it won the self-hosting crowd: less surface, fewer moving parts, one process.
The one place footprint grows is with many sites and complex middleware, where config size and per-request handler chains add up โ but even then Caddy stays lean relative to a multi-tool equivalent. For a VPS or a homelab, Caddy's resource story is a feature.
16. Where Your Data Goes
The data-sovereignty answer for Caddy is mostly clean but has one nuance. Your
request logs (if enabled) record IPs, paths, and user agents, and they live where you configure them โ local files, stdout, or a log shipper โ on infrastructure you control. Your
certificates and ACME account data live in the storage backend you choose, local by default, on your infrastructure. Your
request content is proxied to your backends; Caddy does not store it.
The oneๅคๆต point is certificate issuance itself: when Caddy obtains a public certificate via ACME, it proves domain control to a public CA (Let's Encrypt or ZeroSSL), which means the CA learns that the domain exists and that you requested a certificate for it. That is inherent to public TLS and not specific to Caddy. For fully internal services, use Caddy's
internal CA (
tls internal) and nothing leaves your network at all. For public services, the only data that leaves is the unavoidable certificate-issuance handshake โ no visitor data, no logs, no analytics are sent to the CA or to the Caddy company.
17. What It Costs
Caddy the software is free under Apache-2.0, with no tiers. The real costs are operational: the compute to run it (negligible), the DNS API credentials if you use DNS-01 challenges, the shared storage if you run multiple instances, and the CI/CD to own your xcaddy build if you use plugins. Compared to NGINX Plus (a commercial subscription) or a managed edge, Caddy's TCO is essentially the server it runs on plus your time. For a self-hoster that is about as cheap as infrastructure gets.
The cost you must not skip is patching discipline. A public-facing proxy that is not kept current is a liability, and Caddy's fast security cadence means "set and forget" becomes "set and eventually vulnerable." Budget the upgrade habit, not the licence.
18. Who Should Run It, and Who Shouldn't
Run Caddy if: you serve one or a known set of services and want HTTPS with minimal toil, you value a readable config and a single dependency-free binary, you run a homelab or a VPS or a small-to-mid fleet, and you are comfortable owning a simple xcaddy build if you need DNS plugins. It is the best general-purpose edge for the self-hosting world.
Do not reach for Caddy as your only tool if: you need deep service-mesh traffic management, dynamic Kubernetes-native ingress at scale (use Traefik), or runtime-loadable modules with zero build step. And be honest about the rebuild model โ if your team cannot own a CI build of a custom binary, lean on the standard build and avoid DNS-provider plugins, or pick a server whose plugin model matches your delivery pipeline. Caddy rewards operators who accept its compile-time module philosophy; it frustrates those who fight it.
19. Deploying Caddy: Bare Binary, Docker, and the Official Image
Caddy's deployment is pleasingly simple, and the three shapes are: a bare static binary dropped on a host (ideal for a VPS, no runtime deps), a Docker container using the official
caddy image (500M+ pulls, with Alpine, Windows, and builder variants), and a custom xcaddy-built image when you need plugins. The bare binary is the purest expression of the "single file, done" promise; the Docker image is what most orchestrated deployments use.
The Docker nuance: the standard official image does
not include DNS-provider plugins, so if you need DNS-01 challenges you build your own image from the
caddy:builder variant with an
xcaddy step, or use a prebuilt image from the plugin author. The official image is the safe daily driver; the custom build is for the wildcard/firewalled case. Keep the two distinct in your mind and in your registry, because pulling the standard image when you needed the DNS-plugin image is how a wildcard cert silently fails to renew.
20. Real-World Recipes: A Reverse Proxy in Three Lines
The reason Caddy spread so fast is that common tasks are tiny. A reverse proxy for a local service is three lines:
``
example.com {
reverse_proxy localhost:3000
}
`
That one block obtains the certificate, redirects HTTP to HTTPS, enables HTTP/3, and proxies to your backend. A static site is file_server
with a root
. A redirect is a one-liner. The readability is the feature: a teammate can read the Caddyfile and understand the edge without learning a new DSL. This is deliberately contrasted with NGINX's more verbose server-block syntax, and it is a large part of why Caddy is the default teaching proxy.
The caveat is that readable does not mean trivial at scale. A Caddyfile with dozens of sites, matchers, and middleware chains is still a config to be reviewed and version-controlled. The three-line promise holds for the common case; the hundred-line file holds for the real one. Treat the Caddyfile as code, store it in git, and review changes like any other infrastructure diff.
21. The Templates Module and the XSS Lesson
Caddy ships a templates
module that renders server-side includes, environment variables, and simple logic in served files โ handy for injecting config or dynamic bits into static pages. The June 2026 v2.11.4 release hardened this module's stripHTML
action after a flaw let malformed HTML slip through to downstream output, a classic XSS vector. The lesson is general: any server-side templating that mixes untrusted input into output is an XSS surface, and Caddy's module is no exception.
If you use templates
, treat all interpolated content as untrusted, keep the stripHTML hardening current, and avoid rendering user-controlled data through it. The module is convenient; it is also exactly the kind of feature that turns a "static file server" into a dynamic renderer with a security boundary you must respect. Enable it only where you need it, and keep Caddy patched past the hardening release.
22. Security Middleware: Rate Limiting, Headers, and Forward-Auth
Beyond TLS, Caddy is a competent security edge. It can set security headers (HSTS, CSP, X-Frame-Options) per site, apply basic rate limiting, and โ importantly โ perform forward-auth against an identity provider, sending each request to an auth service that returns allow or deny before the backend sees it. Forward-auth is how you put Authentik or Keycloak (covered elsewhere in this series) in front of services that have no auth of their own, like a raw dashboard or a file browser.
The forward-auth pattern is powerful and worth understanding: Caddy proxies the request to your auth endpoint, which sets a cookie or header on success and returns 200; Caddy then passes the original request to the backend with that assertion. This lets you centrally authenticate a whole estate of auth-less tools through one Caddy config. The operational cost is that the auth service is now on the critical path for every protected request โ if it is down, access breaks. Design the auth dependency as production-grade, because Caddy made it the gatekeeper.
23. Load Balancing and Upstream Health
Caddy's reverse_proxy
is not just a single-target forwarder; it load-balances across multiple upstreams with several policies (round-robin, least-conn, IP-hash, random, and header-based), and it supports active and passive health checks that pull dead upstreams out of rotation. For a service running on two or three nodes, Caddy is a perfectly capable L7 load balancer without a separate HAProxy or cloud ELB in front.
The honest limit, repeated from earlier: Caddy is a thin edge layer. It does not do service-mesh policy, deep traffic splitting, or service-discovery control planes. For "spread requests across my three app containers and stop sending to the dead one," it is exactly right. For "implement a full mesh with weighted canaries and mutual-TLS between every service," it is the edge, not the mesh. Know which job you are hiring it for.
24. Upgrades: Small, Frequent, and Mostly Painless
Caddy's upgrade discipline is light compared to a stateful app. Because the binary is static and the config is a file, upgrading is usually "pull the new image or binary, swap it, reload." The project's fast security cadence means you should upgrade often, and the risk is low because there is little state to migrate. The one thing to watch is config-variable renames between versions โ Caddy occasionally changes a directive or a global option name, and an upgrade that silently breaks is usually the one where an option was renamed.
The plugin rebuild case is the exception: if you run a custom xcaddy image, an upgrade means rebuilding with the new Caddy version and the same plugin set, then re-publishing your image. Automate that build so "upgrade Caddy" is a pipeline run, not a manual session. For the standard build, upgrades are among the least painful in this entire series โ which is exactly why a public-facing proxy you must keep current is a good fit for Caddy.
25. A Practical Sizing and Hardening Checklist
To close the operational loop, a concrete checklist for a production Caddy: bind the admin API to localhost or disable it; set a real CADDY_TLS
storage backend if you run more than one instance; use the internal CA for LAN-only services; keep port 443/UDP open for HTTP/3; enable security headers globally; put forward-auth in front of auth-less backends; scrape the Prometheus endpoint; pin and rebuild your xcaddy image if you use DNS plugins; and subscribe to release notes so the next CVE patch lands promptly. None of these are exotic; together they turn "it serves HTTPS" into "it is a defensible edge."
26. Caddy for Static Sites and the JAMstack Edge
A use case worth calling out: Caddy is an excellent static-file server and JAMstack edge. Point root
at your build output and enable file_server`, and you have a fast, HTTP/3, automatically-TLS'd host for a SPA, a documentation site, or a generated blog โ with no CDN required for modest traffic. For a self-hoster publishing a site, this is the entire stack in one binary: no NGINX, no Certbot, no separate cache.
The scaling note is honest: at very high traffic or global latency targets you will want a CDN in front regardless of server, because a single VPS has one geographic location. Caddy does not replace a CDN; it is the origin that the CDN sits in front of. For everything below "I need edge PoPs on five continents," Caddy serving static files is more than enough, and the zero-config TLS is the part you feel every time you deploy.
27. The Commercial Relationship and Why It Doesn't Trap You
It is fair to ask where the money comes from, given that every feature is free. The commercial entity (Stack Holdings, via ZeroSSL) earns on adjacent services: certificate lifecycle management, hosting, and enterprise support โ not on locking Caddy features. Because the licence is Apache-2.0 and there is no open-core tier, that company holds no lever over the binary you run. They cannot paywall a feature, revoke your licence, or change the terms under your deployed server.
This is the structural contrast with open-core projects we have covered (Rocket.Chat's enterprise edition, Metabase's paid tiers, MinIO's commercial-embedding AGPL): Caddy gives you the whole thing, free, forever, under a permissive licence. The company's survival does not depend on taking features away from you. For a self-hoster who has been burned by "free until it isn't," that is the property that matters most โ and Caddy's is about as clean as infrastructure licensing gets.
28. The Bottom Line
Caddy is the rare infrastructure project that is both more capable and less work than the thing it replaces. Automatic HTTPS alone justifies the switch for most self-hosters; the readable config, the single binary, the live admin API, and the protocol leadership (HTTP/3, ECH, PQC) make it the default edge for a reason. The two things to accept going in are the compile-time plugin model โ own your xcaddy build if you need DNS plugins โ and the multi-instance storage coordination, which is an architectural decision the moment you run more than one copy. Do those two things, keep it patched, and Caddy will serve you quietly for years. It is, in the truest sense, the web server that gets out of your way. If you have been hand-renewing certificates or pasting Certbot hooks for years, the first time Caddy renews one invisibly is the moment the old way stops making sense. It is the rare upgrade that pays you back in reclaimed evenings rather than new chores.
Related
For the rest of a sovereign, self-hosted edge and data stack, these pieces from our series travel with Caddy:
Comments (0)
No comments yet. Be the first to comment!