Envoy doesn’t just terminate TLS; it re-establishes it, turning your edge into a highly configurable, performant TLS termination point that can then speak any protocol to your backend services.
Let’s see this in action. Imagine a simple HTTP service running on localhost:8080. We want to expose it via TLS on localhost:8443 using a self-signed certificate.
First, we need a certificate and key. For testing, openssl is our friend:
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes -subj '/CN=localhost'
This creates server.key (your private key) and server.crt (your public certificate).
Now, for Envoy. Here’s a minimal configuration to terminate TLS for our HTTP service:
static_resources:
listeners:
- name: listener_tls
address:
socket_address:
address: 0.0.0.0
port_value: 8443
filter_chains:
- filter_chain_match:
transport_protocol: "tls"
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: tls_ingress
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: http_service
http_filters:
- name: envoy.filters.http.router
typed_config: {}
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "/etc/envoy/server.crt"
private_key:
filename: "/etc/envoy/server.key"
# Optional: for better security, you can specify TLS versions and cipher suites
# tls_params:
# tls_minimum_protocol_version: TLSv1_2
# cipher_suites:
# - "[ECDHE-ECDSA-AES128-GCM-SHA256]"
# - "[ECDHE-RSA-AES128-GCM-SHA256]"
clusters:
- name: http_service
connect_timeout: 0.25s
type: LOGICAL_DNS
# Use STRICT_DNS for static IPs or when you want to avoid DNS lookups for known hosts.
# dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
# Envoy will connect to the upstream service using plain HTTP here.
# You could also configure TLS for the upstream connection if needed.
load_assignment:
cluster_name: http_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
Save this as envoy.yaml and place server.crt and server.key in /etc/envoy/ (or adjust paths accordingly).
Now, start your backend HTTP service:
python3 -m http.server 8080
And run Envoy:
envoy -c envoy.yaml --service-cluster tls-edge --service-node tls-edge-node
To test, we can use curl with the -k flag (to ignore the self-signed certificate warning):
curl -k https://localhost:8443
You should see the output from your Python HTTP server (likely a directory listing).
The Mental Model:
Envoy acts as a network proxy. When a client connects to localhost:8443, Envoy intercepts that connection. The listener_tls definition tells Envoy to expect TLS on this port. The transport_socket within the filter_chain configures Envoy to use TLS, pointing to your server.crt and server.key for decryption.
Once TLS is established and the encrypted request arrives, Envoy’s http_connection_manager filter takes over. It decodes the HTTP traffic. It then consults the route_config to decide where to send the request. In our case, any request (prefix: "/") is routed to the http_service cluster.
The http_service cluster is configured to use LOGICAL_DNS (or STRICT_DNS if you prefer) to resolve localhost and connect to 127.0.0.1:8080. Crucially, Envoy then initiates a new, plain HTTP connection to 127.0.0.1:8080. The load_assignment specifies the backend address. The http_filters chain, ending with the router filter, handles forwarding the request to the upstream and returning the response.
This pattern allows you to terminate TLS at the edge, inspect HTTP traffic, perform routing, apply security policies, and then forward the request to your backend services using potentially different protocols or TLS configurations. You’re not just terminating TLS; you’re transforming the connection.
One key aspect often overlooked is the explicit configuration of transport_socket within the filter_chain. This is where Envoy learns to become a TLS server. Without it, the listener would just see raw TCP. The common_tls_context is where you provide the credentials, and tls_params offer granular control over the cryptographic handshake itself.
The next step you’ll likely encounter is needing to handle multiple domains or different TLS certificates for various hostnames, which leads into SNI (Server Name Indication) configuration in Envoy.