Short answer

The easiest route to mTLS in Kubernetes is a service mesh (Istio or Linkerd) that automatically injects a sidecar proxy beside each pod and encrypts + authenticates all pod-to-pod traffic without changing your application code. If you want mTLS without a mesh, you issue certificates with cert-manager and mount them into your pods. Both build on an identity model — often SPIFFE — that gives every workload a cryptographically verifiable identity.

Option 1: Service mesh (automatic mTLS)

A service mesh places a sidecar proxy (Envoy in Istio, a lightweight proxy in Linkerd) in each pod. All inbound and outbound traffic flows through the proxy, which establishes mTLS with the peer's proxy. The application only sees plain HTTP on localhost — the proxy handles certificates, rotation and verification.

In Istio you enable mandatory mTLS for a namespace with a PeerAuthentication policy:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: payments
spec:
  mtls:
    mode: STRICT   # reject all non-mTLS traffic in the namespace

STRICT means a pod without a sidecar — or an attacker on the network — cannot talk to the services. PERMISSIVE allows both mTLS and cleartext during a migration.

The strength of a mesh is that the certificates are short-lived (often 24 hours) and rotated automatically. The downside is that they are invisible to your usual monitoring tools — they live in the proxy's memory, not in a file you can inspect.

Option 2: cert-manager (explicit certificates)

If you want mTLS without a full mesh — say, only between two specific services — cert-manager issues certificates from an Issuer and places them in a Kubernetes secret that your pod mounts:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: service-a-client
  namespace: payments
spec:
  secretName: service-a-client-tls
  duration: 2160h        # 90 days
  renewBefore: 360h      # renew 15 days before expiry
  commonName: service-a.payments.svc
  usages:
    - client auth        # clientAuth EKU for the mTLS client role
    - digital signature
  issuerRef:
    name: internal-ca
    kind: ClusterIssuer

Note usages: client auth — that sets the clientAuth EKU that mTLS requires. cert-manager renews automatically before expiry, but if an Issuer fails or a secret is not remounted, a certificate can still expire.

Option 3: SPIFFE/SPIRE (identity, not just keys)

SPIFFE (Secure Production Identity Framework For Everyone) defines a standard identity for workloads: a SPIFFE ID such as spiffe://example.org/ns/payments/sa/service-a, embedded in the certificate's SAN as a URI. SPIRE is the implementation that issues these short-lived SVID certificates to each workload based on its attested identity (e.g. which Kubernetes service account it runs as).

The point is that identity is not an IP or a pod name that changes — it is cryptographically bound to the workload. Istio uses SPIFFE IDs internally, so the two models often go together.

Verify that mTLS is actually active

Having configured mTLS is not the same as it working. Check from inside a pod which certificate the sidecar presents:

# Inspect the cert Istio's sidecar uses
istioctl proxy-config secret deploy/service-a -n payments

# Or test directly against a service via its sidecar port
kubectl exec -it deploy/service-b -n payments -c istio-proxy -- \
  openssl s_client -connect service-a:8080 -showcerts

If you see a SPIFFE URI in the SAN and a short validity window, mTLS is running as expected.

The blind spot: certificates outside the mesh

A mesh handles mTLS neatly inside the cluster. But most organisations also have ingress certificates, certificates on databases outside the cluster, internal CAs with long lifetimes, and legacy services not in the mesh. They fall outside the mesh's automation — and that is exactly where expiry strikes unexpectedly. CertControl discovers and monitors certificates across your whole estate, not just inside one cluster, tracks expiry and validates chains, and warns before a certificate — inside or outside Kubernetes — takes a service down. Start with our mTLS guide.

Frequently asked questions

Do I need a service mesh to get mTLS in Kubernetes?

No, but it is the easiest route if you want mTLS everywhere automatically. For mTLS between a few specific services, cert-manager plus mounted certificates can be simpler and easier to reason about.

What is the difference between Istio and Linkerd for mTLS?

Both provide automatic mTLS via sidecars. Istio is more feature-rich (Envoy-based, many policies), Linkerd is lighter and simpler. For plain mTLS without advanced traffic rules, Linkerd is often sufficient.

What is a SPIFFE ID?

A standardised workload identity of the form spiffe://trust-domain/path, embedded in the certificate's SAN as a URI. It binds identity to the workload rather than to an IP or a pod name that changes.

Do service mesh certificates rotate automatically?

Yes. Both Istio and Linkerd issue short-lived certificates (typically 24 hours) and rotate them continuously with no manual intervention. That is a large part of their value.

Why can't my usual scanners see the mesh certificates?

Because they live in the proxy's memory and are presented only on internal pod-to-pod connections, not on a public port. To inspect them you use mesh tools like istioctl — but ingress and external certificates should still be monitored from the outside.