Caddy can automatically obtain TLS certificates for hostnames it’s never seen before, driven by its on-demand TLS feature.
Let’s see it in action. Imagine you have a Caddyfile like this:
:443 {
tls {
on_demand
}
reverse_proxy localhost:8080
}
Now, if a client tries to connect to https://some-new-domain.example.com that Caddy has never served before, Caddy will:
- Detect the incoming TLS handshake for
some-new-domain.example.com. - Realize it doesn’t have a certificate for this domain.
- Initiate an ACME (Automated Certificate Management Environment) challenge, typically HTTP-01 or TLS-ALPN-01, with Let’s Encrypt or another ACME CA.
- Solve the challenge using the inbound connection itself (if using TLS-ALPN-01) or by serving a temporary file on port 80 (if using HTTP-01) on the
some-new-domain.example.comhostname. - Once the challenge is successful, Caddy will receive the certificate and immediately present it to the client, completing the TLS handshake.
- Subsequent requests to
some-new-domain.example.comwill use the provisioned certificate.
This eliminates the need to pre-configure certificates for every possible hostname, which is a game-changer for dynamic environments, wildcard subdomains, or multi-tenant applications.
The core problem on-demand TLS solves is the management overhead of certificates in systems where the set of hostnames is not known statically. Traditionally, you’d need to:
- Generate a certificate signing request (CSR) for each new domain.
- Submit the CSR to a Certificate Authority (CA).
- Manually import the issued certificate into your web server.
- Set up renewal processes for each certificate.
On-demand TLS automates all of this. Caddy acts as both the TLS terminator and the ACME client.
Internally, when on_demand is enabled, Caddy maintains a cache of certificates. Upon receiving an SNI (Server Name Indication) extension in a Client Hello message during a TLS handshake, Caddy checks its cache. If a valid certificate for that SNI hostname exists, it’s used. If not, and if the SNI hostname matches the configured site address(es), Caddy proceeds with the ACME challenge. The tls directive with on_demand is the primary configuration point, but other options within the tls block can influence this behavior, such as specifying allowed CAs or using a specific ACME server.
The on_demand directive is a powerful shortcut for tls app.letsencrypt.org. You can also specify different ACME servers if needed. For example, to use ZeroSSL:
:443 {
tls {
on_demand
acme_ca https://acme.zerossl.com/v2/DV90
}
reverse_proxy localhost:8080
}
This tells Caddy to use ZeroSSL’s ACME endpoint instead of Let’s Encrypt’s default.
A common point of confusion is how Caddy handles the ACME challenges. For on_demand TLS, Caddy primarily uses the TLS-ALPN-01 challenge by default when serving HTTPS directly on port 443. This means Caddy advertises the ACME-TLS-01 protocol during the TLS handshake. The ACME CA then attempts to connect to Caddy on port 443 using this protocol. Caddy responds with a special handshake that proves it controls the domain. If Caddy is also listening on port 80 and the http directive is present, it can also fall back to the HTTP-01 challenge, where the CA requests a specific file over HTTP.
The key to on-demand TLS working smoothly is ensuring Caddy is accessible from the public internet on ports 80 and/or 443, and that its IP address is correctly resolvable for the domains clients are trying to access. If Caddy cannot be reached by the ACME CA, the certificate provisioning will fail.
When Caddy provisions a certificate on-demand, it stores it locally. The default storage location is typically in Caddy’s data directory (e.g., /var/lib/caddy/.local/share/caddy/pki/authorities/acme/). This cache allows for rapid reuse of certificates for previously seen domains. Caddy also handles the automatic renewal of these provisioned certificates before they expire.
A subtle but important aspect is how Caddy handles multiple hostnames within a single site block. If you have example.com, *.example.com, Caddy will provision certificates for example.com and then any subdomain requested via SNI. The on_demand directive applies to all hostnames matched by the site block.
The next hurdle you’ll likely encounter is understanding how to manage Caddy’s internal state, particularly its certificate and key storage, for backups or migrations.