Last updated: 2026-05-14
Envoy TLS Configuration
Envoy handles TLS in two directions: downstream (terminating TLS from clients, configured on listeners) and upstream (originating TLS to backends, configured on clusters). Both use a common_tls_context block with the same structure; only the wrapper type differs.
Envoy's config is Protobuf-defined; the YAML below is the standard static file format.
Downstream TLS (Listener)
To terminate TLS on a listener, add a transport_socket with DownstreamTlsContext:
static_resources:
listeners:
- name: listener_https
address:
socket_address:
address: 0.0.0.0
port_value: 443
filter_chains:
- transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
require_client_certificate: false
common_tls_context:
tls_params:
tls_minimum_protocol_version: TLSv1_2
tls_maximum_protocol_version: TLSv1_3
cipher_suites:
- ECDHE-ECDSA-AES128-GCM-SHA256
- ECDHE-RSA-AES128-GCM-SHA256
- ECDHE-ECDSA-AES256-GCM-SHA384
- ECDHE-RSA-AES256-GCM-SHA384
- ECDHE-ECDSA-CHACHA20-POLY1305
- ECDHE-RSA-CHACHA20-POLY1305
tls_certificates:
- certificate_chain:
filename: /etc/envoy/ssl/server.crt
private_key:
filename: /etc/envoy/ssl/server.key
validation_context:
trusted_ca:
filename: /etc/envoy/ssl/ca.crt
Setting require_client_certificate: true enables mutual TLS: clients must present a certificate signed by trusted_ca.
cipher_suites applies only to TLS 1.2. TLS 1.3 ciphers are fixed by BoringSSL (AES-256-GCM-SHA384, CHACHA20-POLY1305-SHA256, AES-128-GCM-SHA256) and are not configurable.
Upstream TLS (Cluster)
To originate TLS to a backend, add a transport_socket with UpstreamTlsContext on the cluster. Always configure validation_context; skipping it means Envoy encrypts the connection but doesn't verify the backend's certificate.
clusters:
- name: backend
type: STRICT_DNS
load_assignment:
cluster_name: backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: backend.internal
port_value: 8443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_params:
tls_minimum_protocol_version: TLSv1_2
tls_certificates:
- certificate_chain:
filename: /etc/envoy/ssl/client.crt
private_key:
filename: /etc/envoy/ssl/client.key
validation_context:
trusted_ca:
filename: /etc/envoy/ssl/ca.crt
match_typed_subject_alt_names:
- san_type: DNS
matcher:
exact: backend.internal
match_typed_subject_alt_names pins the expected SAN on the backend's certificate. Without it, Envoy verifies the CA chain but not the hostname, equivalent to verify-ca rather than verify-full.
Mutual TLS
For mTLS in both directions, combine the settings:
- Listener:
require_client_certificate: true+validation_contextwith the client CA - Cluster:
tls_certificateswith the client certificate and key +validation_contextfor the server
In service mesh deployments where a single CA (Vault, cert-manager, etc.) issues all certificates, the same certificate pair is often used for both client and server roles on each workload.
Verification
The Envoy admin interface shows all loaded certificates:
curl http://localhost:9901/certs
This returns certificate details including SANs and expiry dates. Test the TLS handshake directly:
openssl s_client -connect localhost:443 -CAfile /etc/envoy/ssl/ca.crt