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

ShapeFlowPlaintext ownerTypical use
Edge terminationClient —TLS edge —HTTP backendEdge and every internal hop after itLow-sensitivity apps, simple ingress, legacy backends
Re-encryptionClient —TLS edge —TLS backendEdge, then backend; internal network sees ciphertextSensitive services behind a proxy that still needs routing/control
PassthroughClient —TLS through proxy backendBackend onlyProtocols 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 pod

The 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: 8080

This 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.local

The 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-tls

The 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-ca

Kubernetes 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:

HopProtocol on wireWho terminates TLSCertificate name verifiedTrust root
Client edgeHTTPSEdge proxyapi.example.comPublic CA bundle
Edge ingressHTTPS or HTTPIngress or edgeingress hostname if HTTPSPublic or private CA
Ingress backendHTTPS or HTTPBackend service if HTTPSapi.prod.svcInternal 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/health

For 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:

  1. Plaintext is acceptable because the next network is inside the intended trust boundary.
  2. Re-encrypt and verify the next hop.
  3. Use passthrough so the backend owns TLS termination.

Sensitive internal systems usually use option 2 or 3. Option 1 records a deliberate plaintext boundary.

See also

Sources