Envoy Gateway TLS and Security: Set Up HTTPS and Stop Your API from Running Naked
Getting HTTP working is just the warmup. Unless your service lives in the Stone Age, production almost certainly needs HTTPS. This post covers the TLS fundamentals for Envoy Gateway so your APIs stop running in the clear.
How TLS Works in Gateway
The most common approach: terminate TLS at the Gateway listener.
Meaning:
- The client connects via HTTPS
- The Gateway handles the handshake and certificate
- Traffic is then forwarded to your Service unencrypted internally
A basic HTTPS listener looks like this:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: example-certTwo things matter here:
protocol: HTTPStls.mode: Terminate
This means: TLS ends here, and the Gateway handles the certificate.
HTTPS + HTTPRoute vs TLSRoute: What's the Difference
This is important, because many people see TLSRoute and immediately ask:
"Isn't an HTTPS listener already enough? Why do we need a separate TLSRoute?"
The difference is whether the Gateway terminates TLS:
| Scenario | Common Combination | Description |
|---|---|---|
| Gateway terminates TLS, then applies HTTP rules | HTTPS listener + HTTPRoute |
Most common website/API scenario |
| Gateway doesn't decrypt — routes encrypted traffic based on SNI only | TLS listener + TLSRoute |
TLS passthrough scenario |
In other words:
- If you want to inspect
path,header,methodat the Gateway, you need to terminate TLS first, then useHTTPRoute - If you want to preserve end-to-end encryption and not decrypt at the Gateway,
TLSRouteis the right path
Neither approach is "more advanced" — the point is knowing what you want: "terminate TLS and do L7 routing," or "keep encryption intact and do SNI-based routing."
Prepare the Certificate Secret
For test environments, a self-signed certificate works. The official examples use exactly this:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
-subj '/O=example Inc./CN=example.com' \
-keyout example.com.key \
-out example.com.crt
openssl req -out www.example.com.csr -newkey rsa:2048 -nodes \
-keyout www.example.com.key \
-subj "/CN=www.example.com/O=example organization"
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key \
-set_serial 0 -in www.example.com.csr -out www.example.com.crtStore the cert/key as a Kubernetes Secret:
kubectl create secret tls example-cert \
--key=www.example.com.key \
--cert=www.example.com.crtThe Gateway can then reference this Secret via certificateRefs.
⚠️ Self-signed certs are for testing only. In production, use cert-manager or your existing certificate management workflow. Don't carry the demo setup directly into production — that's like surviving on instant noodles: fine occasionally, not sustainable long-term.
Add an HTTPS Listener to an Existing Gateway
If you already have a eg Gateway from the previous post, you can patch it directly:
kubectl patch gateway eg --type=json --patch '
- op: add
path: /spec/listeners/-
value:
name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: example-cert
'Verify the status:
kubectl get gateway/eg -o yamlIf the listener looks healthy, test HTTPS:
curl -v -HHost:www.example.com \
--resolve "www.example.com:443:${GATEWAY_HOST}" \
--cacert example.com.crt \
https://www.example.com/getThis command is great for local validation, especially when DNS isn't wired up yet.
One Gateway, Multiple HTTPS Domains
If you have multiple domains, add multiple HTTPS listeners to the same Gateway.
For example, adding foo.example.com:
listeners:
- name: https-main
protocol: HTTPS
port: 443
hostname: www.example.com
tls:
mode: Terminate
certificateRefs:
- name: example-cert
- name: https-foo
protocol: HTTPS
port: 443
hostname: foo.example.com
tls:
mode: Terminate
certificateRefs:
- name: foo-certDon't forget to update the corresponding HTTPRoute with the right hostname — otherwise the listener might be listening, but the route doesn't know where to send the traffic.
Mental Model for TLSRoute
For TLS passthrough, think of TLSRoute like this:
💡 The
apiVersionforTLSRoutemay differ depending on which version of Gateway API CRDs you've installed. The example below is for conceptual illustration — check your cluster's installed version before applying.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg-tls
spec:
gatewayClassName: eg
listeners:
- name: tls
protocol: TLS
port: 443
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: passthrough-route
spec:
parentRefs:
- name: eg-tls
hostnames:
- "db.example.com"
rules:
- backendRefs:
- name: db-service
port: 5432Common use cases for this pattern:
- Routing based only on SNI hostname
- Preserving encryption all the way to the backend
- No HTTP path/header parsing at the Gateway
So the biggest difference between TLSRoute and HTTPRoute isn't just the name — they're operating at entirely different traffic layers.
Cross-Namespace Certificate References
The official docs highlight a useful capability:
A Gateway can reference certificate Secrets from other namespaces — but it requires a ReferenceGrant.
For example, if the platform team stores certificates centrally in envoy-gateway-system, but the application Gateway is in default, you can't just reference across namespace boundaries without explicit authorization.
Example:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-default-to-read-cert
namespace: envoy-gateway-system
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: default
to:
- group: ""
kind: SecretThis means:
- A
Gatewayin thedefaultnamespace - Is authorized to reference
Secretresources inenvoy-gateway-system
Without a ReferenceGrant, this cross-namespace reference is treated as invalid. It's a protection mechanism — not Gateway API being unnecessarily strict.
One-Line Summary
Envoy Gateway TLS really isn't as mysterious as it sounds: prepare the certificate Secret, configure the HTTPS listener, confirm your route and hostname align — and that's basically it.
💡 The most common beginner mistake isn't that TLS is hard — it's that three things aren't aligned:
hostname,certificateRefs, andHTTPRoute hostnames. If any one of them is off, traffic starts misbehaving.
Next Step
Now that you've covered installation, concepts, routing, and TLS, the next post consolidates this into a set of practices that are less likely to blow up in production: 👉 Best Practices