TLS
On this page
Working example configs for every TLS scenario live in
examples/configs/protocols/:
| Example | Scenario |
|---|---|
| tls-termination | HTTPS listener, plain HTTP upstream |
| tls-http-reencrypt | HTTPS listener, TLS upstream |
| tls-multi-cert | SNI with multiple certificates |
| tls-version-constraint | TLS 1.3 only |
| tls-mtls-listener | Require client certificate |
| tls-mtls-listener-request | Request (optional) client cert |
| tls-mtls-upstream | Client cert to upstream |
| tls-mtls-both | mTLS on both sides |
| tls-verify-disabled | Skip upstream cert verify (dev) |
| upstream-tls | Plain listener, TLS upstream |
| upstream-ca-file | Global CA for all upstreams |
| tcp-tls-termination | TLS on TCP listener |
| tcp-tls-mtls | mTLS on TCP listener |
Listener TLS
Add tls to any listener. PEM format; the cert file
may include the full chain. See tls-termination for
a complete example.
tls:
certificates:
- cert_path: /etc/praxis/tls/cert.pem
key_path: /etc/praxis/tls/key.pem
SNI and Multiple Certificates
Multiple certificates on a single listener enable
SNI-based selection. Entries with server_names match
those hostnames; wildcard entries like *.example.com
match single-level subdomains. Mark exactly one entry with
default: true to serve as the fallback for unmatched
SNI. An entry without server_names that is not marked
default: true is rejected as ambiguous. If no entry
has default: true, unmatched SNI is rejected (no
fallback). See tls-multi-cert.
Certificate Hot-Reload
Certificate hot-reload is enabled by default. Cert and key files are watched for changes. When modified (e.g. by certbot, cert-manager, or Vault PKI), the proxy atomically picks up the new certificate within 500ms. Existing connections are unaffected; only new TLS handshakes use the rotated certificate.
To explicitly disable hot-reload, set hot_reload: false.
Multi-cert SNI configs auto-disable hot-reload.
Hot-reload is compiled in by the config-reload build
feature (on by default); a binary built without it serves
the certificate statically. See
Build Features.
Constraints:
- Hot-reload applies only to single-cert listeners. Multi-cert SNI configs are automatically excluded.
- If the new certificate fails to parse, the proxy logs a warning and continues serving the previous valid certificate. A failed reload is retried on a timer even without a further file change, backing off exponentially up to 60s between attempts, so a file caught mid-write is picked up once it is complete.
Debounce behavior: filesystem events are debounced by 500ms to handle atomic rename patterns used by Kubernetes secret mounts, certbot, and cert-manager. A cert-manager rotation that writes a temp file and then renames it over the original triggers a single reload after the rename completes.
Alternative: graceful restart. Pingora supports graceful restart via SIGHUP with FD passing. This reloads all configuration including certificates, but drains in-flight connections. Use graceful restart when hot-reload is not needed or for config changes beyond certificate rotation.
Minimum TLS Version
min_version restricts the minimum protocol version:
tls12 (default) or tls13. See
tls-version-constraint.
Listener mTLS
Require or request client certificates with
client_ca and client_cert_mode.
| Mode | Behavior |
|---|---|
none | Do not request a client certificate (default) |
request | Ask for a cert but allow connections without one |
require | Reject connections without a valid client cert |
client_ca is required when mode is request or
require. See tls-mtls-listener and
tls-mtls-listener-request.
Certificate Revocation Lists (CRL)
Add crl_paths to client_ca to reject client
certificates that appear on a Certificate Revocation
List. Each path must point to a PEM-encoded CRL file.
Paths containing .. are rejected at validation to
prevent directory traversal.
tls:
client_cert_mode: require
client_ca:
ca_path: /etc/praxis/tls/client-ca.pem
crl_paths:
- /etc/praxis/tls/client-ca.crl
certificates:
- cert_path: /etc/praxis/tls/cert.pem
key_path: /etc/praxis/tls/key.pem
CRL checking applies only to listener client
authentication. Upstream (cluster) CRL checking is
not implemented, so tls.ca.crl_paths on a cluster
definition is rejected at config validation rather
than silently ignored.
Local dev with mkcert
mkcert -install
mkcert localhost 127.0.0.1
Point cert_path and key_path at the generated files.
Cluster TLS
Add tls: to a cluster to TLS-connect to endpoints.
sni sets the backend SNI hostname. verify controls
certificate verification (default: true). See
upstream-tls and tls-verify-disabled.
Upstream mTLS (Client Certificate)
Present a client certificate to upstream servers. See tls-mtls-upstream and tls-mtls-both.
CA Trust
Three levels of CA trust, evaluated in order:
- Per-cluster CA (
tls.ca.ca_path): applies to one cluster only. - Global CA (
runtime.upstream_ca_file): applies to all clusters without their owntls.ca. - System trust store: used when neither of the above is set.
The global CA replaces the system trust store (not additive). If backends use both a private CA and public CAs, create a combined PEM bundle. See upstream-ca-file.
Timeouts
Pingora enforces a 60-second TLS handshake timeout
(hardcoded). For total connection budgets (TCP + TLS),
use total_connection_timeout_ms on the cluster. See
configuration.md for details.
Certificate and Key Security
Private keys should have restrictive file permissions. Praxis warns at startup if keys are group or world readable.
chmod 600 /etc/praxis/tls/key.pem
chown praxis:praxis /etc/praxis/tls/key.pem
Don’t store private keys in version control or unencrypted on disk. Use a secrets manager or encrypted storage solution.
Cipher Suites
Praxis uses rustls, which supports TLS 1.2 and 1.3 only. No weak cipher suites are available.
By default, rustls selects cipher suites automatically.
To restrict the set, specify cipher_suites on the
listener TLS block. When set, only the listed suites
are offered during the TLS handshake. An empty list is
rejected at validation.
Available Suites
TLS 1.3:
| Config value | Suite |
|---|---|
tls13_aes_128_gcm_sha256 | AES-128-GCM with SHA-256 |
tls13_aes_256_gcm_sha384 | AES-256-GCM with SHA-384 |
tls13_chacha20_poly1305_sha256 | ChaCha20-Poly1305 with SHA-256 |
TLS 1.2:
| Config value | Suite |
|---|---|
tls12_ecdhe_ecdsa_with_aes_128_gcm_sha256 | ECDHE-ECDSA AES-128-GCM |
tls12_ecdhe_ecdsa_with_aes_256_gcm_sha384 | ECDHE-ECDSA AES-256-GCM |
tls12_ecdhe_ecdsa_with_chacha20_poly1305_sha256 | ECDHE-ECDSA ChaCha20-Poly1305 |
tls12_ecdhe_rsa_with_aes_128_gcm_sha256 | ECDHE-RSA AES-128-GCM |
tls12_ecdhe_rsa_with_aes_256_gcm_sha384 | ECDHE-RSA AES-256-GCM |
tls12_ecdhe_rsa_with_chacha20_poly1305_sha256 | ECDHE-RSA ChaCha20-Poly1305 |
TLS 1.2 suites cannot be used when min_version is
tls13. See tls-cipher-suites.
tls:
cipher_suites:
- tls13_aes_256_gcm_sha384
- tls13_chacha20_poly1305_sha256
certificates:
- cert_path: /etc/praxis/tls/cert.pem
key_path: /etc/praxis/tls/key.pem