Skip to content

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 curves overrides the defaults and disables the default post-quantum group (X25519MLKEM768) unless you include x25519mlkem768 in 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/tls does 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/tls is 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/tls does 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.


Related Guides

View all Web Servers & Proxies guides →

Configured TLS? Now Monitor It.

Generator Labs alerts you before certificates expire, get revoked, or fail chain validation, across HTTPS, SMTPS, IMAPS, LDAPS, and more.

Certificate Monitoring →