Caddy’s automatic HTTPS is amazing, but sometimes you need to bring your own TLS certificates, maybe for internal services that Caddy can’t get public certificates for, or if you have specific certificate requirements.

Let’s see Caddy serve a site using a custom certificate. Imagine we have a domain internal.example.com that we want to serve over HTTPS with a certificate we generated ourselves.

First, we need our certificate and private key files. Let’s assume they are named internal.example.com.crt and internal.example.com.key respectively, and they are located in a directory that Caddy can access. For this example, we’ll put them in /etc/caddy/certs/.

Here’s a minimal Caddyfile to achieve this:

internal.example.com {
    tls /etc/caddy/certs/internal.example.com.crt /etc/caddy/certs/internal.example.com.key
    respond "Hello from custom TLS!"
}

When Caddy starts with this configuration, it will load internal.example.com.crt and internal.example.com.key for the internal.example.com site. The tls directive, when given file paths, tells Caddy not to attempt ACME challenges for a certificate, but to use the provided files directly.

The respond directive is just a simple placeholder to show that the site is being served. In a real-world scenario, this would likely be a reverse_proxy directive or something similar.

Now, let’s dive into the mental model of how Caddy handles this. By default, Caddy is a reverse proxy and TLS server that automatically obtains and renews certificates from Let’s Encrypt (or ZeroSSL). It manages a certificate cache, handles ACME challenges (HTTP-01 and TLS-ALPN-01), and seamlessly renews certificates before they expire. This is its "auto-HTTPS" mode.

When you provide explicit certificate files using the tls directive with file paths, you’re essentially telling Caddy to bypass its automatic certificate management for that specific site. Caddy will load these files and use them for TLS termination. It still handles the TLS handshake, cipher suite negotiation, and other TLS-related aspects, but it trusts your provided certificate and key instead of obtaining its own.

The key advantage here is control. You can use certificates issued by your internal Certificate Authority (CA), or certificates obtained through other means that Caddy’s automatic system can’t access. This is crucial for internal-only services, development environments, or situations where you have specific compliance or security requirements for your certificates.

The tls directive is quite flexible. You can specify just the certificate file, and Caddy will try to infer the private key based on common naming conventions (e.g., if you provide cert.pem, it looks for cert.key). However, it’s always best practice to explicitly provide both the certificate and the key for clarity and reliability.

Here’s a more robust example with a reverse proxy:

internal.example.com {
    tls /etc/caddy/certs/internal.example.com.crt /etc/caddy/certs/internal.example.com.key
    reverse_proxy localhost:8080
}

In this case, Caddy will use your custom certificate to serve internal.example.com and then forward incoming requests to a backend service running on localhost:8080. The connection from Caddy to localhost:8080 is not affected by the custom TLS certificate; it’s a separate connection that can be plain HTTP or its own TLS, depending on how the backend service is configured.

One thing that trips people up is that Caddy’s automatic certificate management is so good, they forget you can simply tell it to not do it. The tls directive is not just for configuring how Caddy gets certificates (like choosing a specific ACME issuer or using DNS challenges), but also for providing them directly. If Caddy is trying to get a certificate for internal.example.com and failing because it can’t reach the internet or perform ACME challenges, but you do have a certificate, the tls directive with file paths is the way to go.

When you configure Caddy with custom certificates, Caddy does not attempt to validate domain ownership via ACME challenges. It trusts that you, the administrator, have provided the correct certificate for the domain. This means you are responsible for ensuring the certificate is valid, trusted by your clients, and renewed before it expires. If you forget to renew your custom certificate, Caddy will continue to serve the expired certificate, and clients will receive security warnings or be unable to connect.

The next step after mastering custom certificates is often integrating Caddy with an internal CA for automated issuance and revocation within your organization, which involves configuring the tls directive with a custom CA endpoint.

Want structured learning?

Take the full Caddy course →