TLS termination boundaries are the points in a request path where encrypted traffic becomes plaintext. A request can cross several TLS connections before it reaches the application: browser to edge proxy, edge proxy to ingress, ingress to backend service, service to another service. Each connection has its own certificate, trust root, hostname check, and plaintext owner.
For each hop, record three facts: which component terminates TLS, which component can read plaintext, and whether the next hop starts a new verified TLS connection.
Termination Boundaries
The diagram separates three TLS segments. The public edge can terminate the browser connection and then open a different connection toward the cluster. That second connection needs its own policy: HTTP, verified HTTPS, or byte-level passthrough.
TLS termination is the point where a component decrypts the connection and sees HTTP or application protocol bytes. After termination, the component can inspect, log, modify, route, or forward the request. An ingress proxy often needs this access to route HTTP, so the architecture has to account for that plaintext boundary.
Re-encryption means the proxy opens a new TLS connection to the next hop. The proxy sees plaintext between the inbound and outbound connections. The downstream network carries ciphertext, and the proxy verifies the backend certificate.
Passthrough means an intermediate proxy forwards encrypted bytes without terminating TLS. The backend sees the original client TLS connection. The proxy can route on pre-HTTP metadata such as SNI (Server Name Indication, the hostname sent in the TLS ClientHello). HTTP paths and headers become visible only at the backend termination point.
Three Common Shapes
| Shape | Flow | Plaintext owner | Typical use |
|---|---|---|---|
| Edge termination | Client —TLS⇒ edge —HTTP⇒ backend | Edge and every internal hop after it | Low-sensitivity apps, simple ingress, legacy backends |
| Re-encryption | Client —TLS⇒ edge —TLS⇒ backend | Edge, then backend; internal network sees ciphertext | Sensitive services behind a proxy that still needs routing/control |
| Passthrough | Client —TLS through proxy⇒ backend | Backend only | Protocols that need client certs end-to-end, services that own all TLS policy |
Pick the shape by choosing the plaintext owner and the component that authenticates the backend.
Request Path Example
Suppose a request enters through a managed tunnel or CDN, reaches an ingress controller, and then reaches a service:
client -> public edge -> ingress controller -> backend podThe client sees https://api.example.com. The public edge presents a public certificate and terminates the browser’s TLS connection. The edge now holds plaintext HTTP and forwards the request into the cluster. A simple ingress can route that request to an HTTP service port:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
namespace: prod
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 8080This manifest creates an HTTP backend hop. If the edge also forwards HTTP to the ingress, the request is plaintext from the edge onward. That may fit a low-sensitivity app. A secrets service, identity provider, admin plane, or payment system usually needs a verified TLS segment closer to the backend.
Backend Verification
For backend verification, the backend service has its own certificate, usually from an internal CA. The ingress connects to the backend over HTTPS and verifies the backend certificate before forwarding traffic.
The backend certificate is a normal service certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-internal-tls
namespace: prod
spec:
secretName: api-internal-tls
issuerRef:
name: internal-ca
kind: ClusterIssuer
dnsNames:
- api.prod.svc
- api.prod.svc.cluster.localThe backend listener mounts that secret and serves TLS:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: prod
spec:
template:
spec:
containers:
- name: api
image: example/api:latest
args:
- --listen=https://0.0.0.0:8443
- --tls-cert=/tls/tls.crt
- --tls-key=/tls/tls.key
volumeMounts:
- name: tls
mountPath: /tls
readOnly: true
volumes:
- name: tls
secret:
secretName: api-internal-tlsThe proxy also needs the issuing CA. In Traefik, a ServersTransport can carry the root CA bundle used to validate backend servers:
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
name: api-backend-tls
namespace: prod
spec:
rootCAsSecrets:
- internal-caKubernetes Ingress, Traefik IngressRoute, Gateway API, and service meshes expose this wiring differently. The proxy still needs two pieces of state: https for the backend connection and the CA bundle that issued the backend certificate.
The CA Boundary
Public CAs authenticate public DNS names for external clients. Browsers and operating systems already trust them. They are the right fit for api.example.com.
Internal CAs authenticate private service names, cluster DNS, mTLS identities, and operational endpoints. Internal clients need the internal CA bundle. This can be mounted as a Kubernetes Secret, distributed through a service mesh, baked into a workload image, or managed through a platform certificate bundle.
Keep these jobs separate. A public certificate for api.example.com authenticates the public name. An internal certificate for api.prod.svc authenticates the service name for clients that trust the internal CA. The name set and the trust root must match the client path.
What To Log And Verify
For each hop, record four facts:
| Hop | Protocol on wire | Who terminates TLS | Certificate name verified | Trust root |
|---|---|---|---|---|
| Client → edge | HTTPS | Edge proxy | api.example.com | Public CA bundle |
| Edge → ingress | HTTPS or HTTP | Ingress or edge | ingress hostname if HTTPS | Public or private CA |
| Ingress → backend | HTTPS or HTTP | Backend service if HTTPS | api.prod.svc | Internal CA |
The first row alone proves external HTTPS. The other rows describe the internal security boundary.
Use packet-level or client-level checks where possible:
# Does the backend serve TLS at all?
openssl s_client \
-connect api.prod.svc:8443 \
-servername api.prod.svc \
-CAfile internal-ca.pem </dev/null# Does the app endpoint work when the client verifies the internal CA?
curl --cacert internal-ca.pem https://api.prod.svc:8443/healthFor proxies, check the rendered config or CRDs that control the downstream hop.
Failure Modes
Plaintext after the edge. The browser-to-edge segment uses HTTPS, while internal components after termination receive HTTP. Browser UI shows the first segment.
Re-encryption without verification. Some proxies can connect to backend HTTPS while skipping certificate verification. The network segment carries ciphertext, yet any active interceptor that can present a certificate can sit in the path.
Backend identity mismatch. The backend serves TLS and the proxy trusts the CA. The proxy verifies api.prod.svc, while the certificate only contains api.example.com. See Certificate SANs and Access Paths for the name-matching mechanics.
CA bundle drift. The backend rotates to a certificate from a new internal CA, while the ingress or workload still trusts the old CA. The service, DNS, and certificate file all exist, but verification fails.
Design Rule
Draw the request path and mark every decryption point. For each segment after a decryption point, choose one policy:
- Plaintext is acceptable because the next network is inside the intended trust boundary.
- Re-encrypt and verify the next hop.
- Use passthrough so the backend owns TLS termination.
Sensitive internal systems usually use option 2 or 3. Option 1 records a deliberate plaintext boundary.