Caddy’s automatic HTTPS is so good because it’s not just a feature; it’s Caddy’s raison d’être, and it automates the entire lifecycle of TLS certificates for you, transparently.

Let’s watch Caddy in action. Imagine you have a simple Caddyfile:

example.com {
    respond "Hello from Caddy!"
}

When Caddy starts with this configuration and example.com is a domain it can reach over the internet, it immediately tries to get a certificate for it. It doesn’t wait for a user to visit; it proactively reaches out.

Here’s what’s happening under the hood. Caddy implements the ACME (Automatic Certificate Management Environment) protocol, which is the standard way for clients (like Caddy) to interact with Certificate Authorities (CAs) to obtain and manage digital certificates. The most well-known ACME CAs are Let’s Encrypt and ZeroSSL, and Caddy supports both.

When Caddy needs a certificate for example.com, it initiates an ACME challenge. There are two primary methods: HTTP-01 and TLS-ALPN-01.

HTTP-01 Challenge:

  1. Caddy’s Role: Caddy starts a temporary HTTP server on port 80 (or whichever port is configured for HTTP).
  2. ACME CA’s Role: The CA’s server asks Caddy for a specific, unique token. Caddy is instructed to serve this token at a specific URL path: http://example.com/.well-known/acme-challenge/<token>.
  3. Verification: Caddy responds with the token. The CA’s server then makes an HTTP GET request to http://example.com/.well-known/acme-challenge/<token> and verifies that the response body matches the expected token. If it does, the CA knows Caddy controls example.com.

TLS-ALPN-01 Challenge:

  1. Caddy’s Role: Caddy starts a temporary TLS server on port 443.
  2. ACME CA’s Role: The CA’s server initiates a TLS handshake with Caddy. During the handshake, Caddy advertises support for the acme-validation ALPN (Application-Layer Protocol Negotiation) protocol.
  3. Verification: If Caddy successfully negotiates acme-validation, the CA knows Caddy controls example.com via TLS on port 443. This is often preferred as it doesn’t require opening port 80.

Once the challenge is successfully completed, the CA issues a certificate for example.com and sends it back to Caddy. Caddy then automatically configures its TLS listener to use this new certificate, and it will serve HTTPS traffic for example.com immediately.

The "magic" isn’t just obtaining the certificate; it’s managing its entire lifecycle. Caddy automatically handles renewals. Certificates are typically valid for 90 days. Caddy monitors the expiration date and, well before it expires, it initiates the ACME challenge process again to obtain a new certificate. This renewal process is identical to the initial issuance.

The "zero config" aspect comes from Caddy’s ability to automatically detect the domains it needs to serve. If you have a Caddyfile like:

my-site.com, another-site.org {
    reverse_proxy localhost:8080
}

Caddy will attempt to obtain certificates for both my-site.com and another-site.org. It’s smart enough to know that if a request comes in for my-site.com, it needs a certificate for that specific domain.

Here’s a look at Caddy’s internal state when it’s managing certificates. When Caddy obtains a certificate, it stores it in a local directory. The default location depends on your OS:

  • Linux: /var/lib/caddy/.local/share/caddy/pki/
  • macOS: $HOME/.local/share/caddy/pki/
  • Windows: %APPDATA%\caddy\.local\share\caddy\pki\

Inside this pki directory, you’ll find subdirectories for authorities (where Caddy stores its internal CA certificates and keys if it’s acting as one) and acme (where it stores information about ACME accounts and issued certificates).

Within acme/certs/, you’ll find directories named after the domains for which Caddy has obtained certificates. Inside each domain’s directory, you’ll see files like:

  • cert.pem: The actual X.509 certificate.
  • key.pem: The private key associated with the certificate.
  • chain.pem: The intermediate certificate chain.
  • private.json: Metadata about the certificate.

When Caddy starts, it scans this directory, loads any existing certificates, and checks their expiry. If a certificate is nearing expiry, it will automatically queue up a renewal.

The most surprising true thing about Caddy’s automatic HTTPS is that it uses its own ACME client implementation, rather than relying on external tools like certbot. This allows for tighter integration and the ability to perform the TLS-ALPN-01 challenge, which is more efficient and secure as it doesn’t require opening port 80.

The practical implication of Caddy’s built-in ACME client is that it can obtain and renew certificates even if your server is behind a restrictive firewall, as long as it can establish outbound connections to the ACME CA servers and accept inbound connections on port 443 (for TLS-ALPN-01) or port 80 (for HTTP-01).

If you ever need to manually inspect or manage these certificates, you can find them in the pki/acme/certs/ directory.

The next concept you’ll want to explore is how Caddy handles multiple domains on a single IP address and the nuances of certificate pinning.

Want structured learning?

Take the full Caddy course →