Last updated: 2026-06-25
Caddy TLS/SSL Configuration Guide
This guide covers TLS/SSL settings for Caddy, a modern web server that provides automatic HTTPS out of the box. Caddy automatically obtains and renews certificates from Let's Encrypt, configures strong TLS defaults, and enables HSTS, making it one of the most secure web servers by default.
Prerequisites
- Caddy 2.0 or later
- A publicly accessible domain name (for automatic certificate issuance)
Automatic HTTPS (Default Behavior)
Caddy's defining feature is automatic HTTPS. With a minimal Caddyfile, Caddy will:
- Obtain a TLS certificate from Let's Encrypt (or ZeroSSL)
- Automatically renew the certificate before it expires
- Redirect HTTP to HTTPS
- Enable HSTS
- Use strong TLS 1.2 and TLS 1.3 defaults
A basic Caddyfile with automatic TLS:
example.com {
root * /var/www/html
file_server
}
That's it. No TLS configuration is needed for most deployments. Caddy's defaults are already strong and secure.
Default TLS Settings
Out of the box, Caddy configures:
- Protocols: TLS 1.2 and TLS 1.3
- TLS 1.3 cipher suites: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256
- TLS 1.2 cipher suites: ECDHE+AESGCM, ECDHE+CHACHA20 (only forward-secret AEAD ciphers)
- ECDH curves: X25519MLKEM768 (post-quantum hybrid, default since Caddy 2.10), X25519, P-256, P-384
- HSTS: Enabled automatically
Custom TLS Configuration
If you need to customize TLS settings (for compliance, auditing, or specific client requirements), use the tls directive in your Caddyfile.
Protocol Versions
Restrict to specific TLS versions:
example.com {
tls {
protocols tls1.2 tls1.3
}
root * /var/www/html
file_server
}
Cipher Suites
Caddy uses Go's crypto/tls cipher names (IANA format). To explicitly set cipher suites:
example.com {
tls {
ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
}
}
TLS 1.3 cipher suites cannot be customized in Go's TLS stack. All three TLS 1.3 ciphers are always enabled, which is fine since they are all strong.
ECDH Curves
Specify the elliptic curves for key exchange:
example.com {
tls {
curves x25519 secp256r1 secp384r1
}
}
Explicitly setting
curvesoverrides the defaults and disables the default post-quantum group (X25519MLKEM768) unless you includex25519mlkem768in the list.
Mutual TLS (mTLS)
Standard TLS authenticates only the server. Mutual TLS adds client authentication, requiring the connecting client to also present a certificate. This is an optional hardening step, not required for standard web deployments. It is most useful for internal services and APIs where you control all connecting clients.
Add a client_auth block inside the tls directive:
example.com {
tls {
protocols tls1.2 tls1.3
client_auth {
mode require_and_verify
trusted_ca_cert_file /etc/caddy/ssl/client-ca.crt
}
}
root * /var/www/html
file_server
}
Available mode values:
- request - Request a client certificate but do not require or verify it
- require - Require a client certificate but do not verify it against a CA
- verify_if_given - Verify the certificate if one is provided, but do not require it
- require_and_verify - Require and verify the client certificate against the trusted CA (recommended for mTLS)
See RFC 8446 ยง4.3.2 for the TLS Certificate Request specification, and Wikipedia: Mutual authentication for a general overview.
Using Your Own Certificates
To use externally obtained certificates instead of automatic issuance:
example.com {
tls /etc/caddy/ssl/fullchain.pem /etc/caddy/ssl/privkey.pem
}
Internal (Self-Signed) Certificates
For internal services that don't need publicly trusted certificates:
internal.example.com {
tls internal
}
Caddy will generate a locally-trusted certificate using its built-in CA.
HSTS
Caddy enables HSTS automatically for public-facing sites. To customize the HSTS header:
example.com {
header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
}
On-Demand TLS
For environments with many domains, Caddy supports on-demand TLS: certificates are obtained at the time of the first TLS handshake:
{
on_demand_tls {
ask http://localhost:5555/check
}
}
https:// {
tls {
on_demand
}
}
The ask endpoint should verify that the requested domain is authorized before Caddy obtains a certificate for it.
Complete Custom Configuration Example
{
# Global options
email admin@example.com
}
example.com {
tls {
protocols tls1.2 tls1.3
ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
curves x25519 secp256r1 secp384r1
}
header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
root * /var/www/html
file_server
}
Security Notes
Caddy uses Go's crypto/tls package, which has a different vulnerability history than OpenSSL:
- POODLE (CVE-2014-3566, 2014): SSL 3.0 has never been supported in Go's TLS implementation.
- BEAST (CVE-2011-3389, 2011): Mitigated by recommending TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
- CRIME (CVE-2012-4929, 2012): TLS compression is not supported in Go's
crypto/tls. - Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
- FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers have never been supported in Go's
crypto/tls. - LOGJAM (CVE-2015-4000, 2015): DHE key exchange is not offered by default in Go; only ECDHE is used.
- Sweet32 (CVE-2016-2183, 2016): 3DES was removed from Go's default cipher list in Go 1.23 (August 2024) and is excluded from the recommended configuration.
- ROBOT (2017): Static RSA key exchange is not offered in Go's
crypto/tls; only ECDHE is used. - Downgrade attacks: Go's
crypto/tlsdoes not perform insecure version-fallback retries, and TLS 1.3 has built-in downgrade protection.
The following are not addressable through TLS configuration alone:
- Heartbleed (CVE-2014-0160, 2014): Not applicable. Go's
crypto/tlsis an independent TLS implementation and was never affected by Heartbleed. - BREACH (CVE-2013-3587, 2013): Exploits HTTP-level response compression (gzip/deflate on responses). Mitigated at the application layer by disabling HTTP compression or using BREACH countermeasures; TLS configuration cannot prevent it.
- DROWN (CVE-2016-0800, 2016): Not applicable. Go's
crypto/tlsdoes not support SSLv2.
Verification
Validate and reload the Caddy configuration:
caddy validate --config /etc/caddy/Caddyfile
caddy reload --config /etc/caddy/Caddyfile
Test the TLS connection:
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
Check Caddy's certificate management status:
caddy trust
Test your configuration externally with the Mr.DNS SSL/TLS Certificate Check.