TLS
TLS configuration splits into two places:
- Certificate paths (
cert_path,key_path) and optional mTLS (client_ca_path) belong to each[[domains]]entry. Each hostname can have its own certificate and client-CA bundle. See Routes & Domains. - Transport options (
alpn,[tls.options],[tls.session_resumption]) live under the top-level[tls]section. Static: requires restart. Certificate file contents reload separately (see Certificate rotation).
Omit the [tls] section entirely to run as plain HTTP.
How SNI and routing relate
Section titled “How SNI and routing relate”These are independent:
| Layer | Input | What it selects |
|---|---|---|
| TLS handshake | ClientHello SNI | Which certificate (and mTLS verifier) to present |
| HTTP request | :authority / Host |
Which domain + route (and backend) |
SNI is not used for routing. That keeps HTTP/2 connection coalescing correct: one TLS session can carry requests for several hosts that share a certificate, each with its own :authority.
Certificate selection (SNI): exact host → single-label wildcard (*.example.com) → default cert (the catch-all / host-less domain). No SNI (e.g. IP-literal clients) also gets the default cert when sni_strict is off.
Minimal HTTPS
Section titled “Minimal HTTPS”Set cert_path and key_path on each domain, and declare ALPN under [tls]:
tls: alpn: - "h2" - "http/1.1"
domains: - host: "api.example.com" cert_path: "/config/certs/api.crt" key_path: "/config/certs/api.key" routes: - prefix: "/" backend: "backend-a:9000"[tls]alpn = ["h2", "http/1.1"]
[[domains]]host = "api.example.com"cert_path = "/config/certs/api.crt"key_path = "/config/certs/api.key"
[[domains.routes]]prefix = "/"backend = "backend-a:9000"Multiple domains, multiple certificates
Section titled “Multiple domains, multiple certificates”Each [[domains]] entry can carry its own certificate. The catch-all domain (no host) provides the default certificate, used when SNI is absent or unrecognized (unless sni_strict is on).
tls: alpn: ["h2", "http/1.1"]
domains: - host: "api.example.com" cert_path: "/config/certs/api.crt" key_path: "/config/certs/api.key" routes: - prefix: "/" backend: "api:9000"
- host: "*.internal.example.com" cert_path: "/config/certs/wildcard-internal.crt" key_path: "/config/certs/wildcard-internal.key" routes: - prefix: "/" backend: "internal:9001"
- # catch-all → default cert cert_path: "/config/certs/default.crt" key_path: "/config/certs/default.key" routes: - prefix: "/" backend: "fallback:9002"[tls]alpn = ["h2", "http/1.1"]
[[domains]]host = "api.example.com"cert_path = "/config/certs/api.crt"key_path = "/config/certs/api.key"
[[domains.routes]]prefix = "/"backend = "api:9000"
[[domains]]host = "*.internal.example.com"cert_path = "/config/certs/wildcard-internal.crt"key_path = "/config/certs/wildcard-internal.key"
[[domains.routes]]prefix = "/"backend = "internal:9001"
[[domains]] # catch-all → default certcert_path = "/config/certs/default.crt"key_path = "/config/certs/default.key"
[[domains.routes]]prefix = "/"backend = "fallback:9002"Certificate rotation
Section titled “Certificate rotation”Certificates are not watched on their own in isolation from config. The proxy re-reads cert/key (and client_ca_path) on every config reload: SIGHUP, or a change to the config file / PEM paths when [reload].watch is enabled (default true, debounced by [reload].debounce_secs, default 60). Paths stay the same; new connections pick up the new certificate, existing ones keep the old until they close.
Reload is best-effort per domain: if one domain’s new cert fails to load, that domain keeps its previously serving certificate while the others still swap. Cipher suites, ALPN, and session-resumption on/off stay as built at startup ([tls] is static); the process-wide ticket key is shared across rebuilds so tickets remain valid after a cert reload.
There is no built-in ACME; point config at files another process renews (cert-manager, acme.sh, Vault, etc.).
Options
Section titled “Options”Use [tls.options] to restrict versions, cipher suites, key-exchange groups, or enable SNI strict mode. These settings are enforced by the TLS stack.
TLS versions:
versions(ormin_version/max_version) restrict what the acceptor offers. Empty / unset keeps TLS 1.2 and 1.3. Do not set both an explicitversionslist and a min/max bound.Order matters for
cipher_suitesandcurve_preferences: first entry is most preferred. A non-empty list replaces the provider defaults entirely (it does not merge).Curves: leaving
curve_preferencesempty keeps the provider’s post-quantum-first defaults. Supported groups:X25519MLKEM768,SECP256R1MLKEM768(post-quantum hybrids),X25519,secp256r1,secp384r1. If you set a list, keep a PQ hybrid first if you want PQ protection.
tls: alpn: - "h2" - "http/1.1" options: sni_strict: false versions: - "1.2" - "1.3" cipher_suites: - "TLS13_AES_128_GCM_SHA256" - "TLS13_AES_256_GCM_SHA384" - "TLS13_CHACHA20_POLY1305_SHA256" - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" curve_preferences: - "X25519MLKEM768" - "SECP256R1MLKEM768" - "X25519" - "secp256r1" - "secp384r1"[tls]alpn = ["h2", "http/1.1"]
[tls.options]sni_strict = falseversions = ["1.2", "1.3"]cipher_suites = [ "TLS13_AES_128_GCM_SHA256", "TLS13_AES_256_GCM_SHA384", "TLS13_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",]curve_preferences = [ "X25519MLKEM768", "SECP256R1MLKEM768", "X25519", "secp256r1", "secp384r1",]SNI strict mode
Section titled “SNI strict mode”sni_strict = true disables the default-certificate fallback entirely (parity with Traefik’s sniStrict: true):
- A connection whose SNI matches no domain cert is rejected with
unrecognized_name. - A connection that sends no SNI (IP-literal clients, RFC 6066) is also rejected.
Leave it false (default) if you need IP-literal HTTPS access or no-SNI clients to work via the catch-all / default cert.
Misdirected requests (HTTP 421)
Section titled “Misdirected requests (HTTP 421)”On coalesced HTTP/2 connections, any request whose host is served by a different certificate than the one the SNI selected is rejected automatically with 421 Misdirected Request. This is the same default behaviour as nginx and Apache mod_http2 and is not configurable. Hosts sharing one certificate (wildcard or SAN) still coalesce normally.
Client authentication (mTLS)
Section titled “Client authentication (mTLS)”mTLS is per-domain via client_ca_path on a [[domains]] entry — there is no listener-wide [tls.client_auth]. Domains with a CA bundle require a client certificate; domains without it stay public. One listener can mix both. mTLS domains never resume a TLS session (client cert re-verified every connection).
domains: - host: "admin.example.com" cert_path: "/config/certs/admin.crt" key_path: "/config/certs/admin.key" client_ca_path: "/config/certs/ca.crt" routes: - prefix: "/" backend: "admin:9000"[[domains]]host = "admin.example.com"cert_path = "/config/certs/admin.crt"key_path = "/config/certs/admin.key"client_ca_path = "/config/certs/ca.crt"
[[domains.routes]]prefix = "/"backend = "admin:9000"A request whose Host names an mTLS domain is rejected with 421 unless it arrives over a TLS session established for that same domain (plaintext / no-SNI / foreign SNI are rejected). Failures to load cert or CA material never fall back to a config without a client verifier. Details: SETTINGS.md — Mutual TLS.
Session resumption
Section titled “Session resumption”Stateless session tickets only (TLS 1.2 and 1.3). There is no server-side session cache and no max_sessions key. Enabled by default; toggle with session_resumption.enabled. mTLS domains never resume.
tls: session_resumption: enabled: true[tls.session_resumption]enabled = trueRelated
Section titled “Related”- Security: response headers (HSTS, CSP) and forwarding behavior
- Configuration overview: full top-level index, including
[reload]