PeerAuthentication is the Istio mechanism that enforces mutual TLS (mTLS) between services.

Let’s see it in action. Imagine we have two services, frontend and backend, running in our Kubernetes cluster. By default, Istio allows unencrypted communication between them. We want to change that.

First, we need to ensure Istio is configured to prefer or require mTLS. This is done at the mesh level with a MeshPeerAuthentication policy.

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

This policy, applied in the istio-system namespace, sets the mesh-wide default to STRICT mTLS. STRICT means that all communication within the mesh must use mTLS. If a service tries to connect without mTLS, the connection will be dropped. Other modes include PERMISSIVE (allows both mTLS and plaintext) and DISABLED.

Now, to enforce mTLS for specific workloads, we use PeerAuthentication policies scoped to a particular namespace or workload. Let’s enforce mTLS for the backend service.

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: backend-mtls
  namespace: my-app-namespace
spec:
  selector:
    matchLabels:
      app: backend
  mtls:
    mode: STRICT

This policy targets pods with the label app: backend in the my-app-namespace. When a frontend service (or any other service) tries to connect to a backend pod, Istio’s Envoy proxies will automatically negotiate an mTLS connection. The frontend proxy will present its certificate, and the backend proxy will verify it against the trust root configured in Istio. Similarly, the backend proxy will present its certificate, and the frontend proxy will verify it. If verification fails for either side, the connection is terminated.

What makes this powerful is that you don’t need to modify your application code. Istio handles the certificate management, rotation, and TLS handshake entirely at the proxy level. The application code simply makes a standard HTTP or gRPC call to backend.my-app-namespace.svc.cluster.local. The Envoy sidecar intercepts this traffic, secures it with mTLS, and forwards it to the destination Envoy proxy, which then decrypts it and forwards it to the backend application.

The trust for these certificates is managed by Istio’s Certificate Authority (CA). By default, Istio installs its own self-signed CA. When you enable mTLS, Istio automatically provisions certificates for each workload. These certificates are signed by the Istio CA and are valid for a specific duration (e.g., 24 hours), after which they are automatically rotated.

Let’s say you want to allow frontend to talk to backend using mTLS, but you don’t want to enforce it mesh-wide yet. You can apply the PeerAuthentication policy only to the backend service as shown above, and then create a DestinationRule to ensure the frontend uses mTLS when connecting to backend.

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: backend
  namespace: my-app-namespace
spec:
  host: backend.my-app-namespace.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

The ISTIO_MUTUAL mode in DestinationRule tells the source workload’s Envoy proxy to initiate an mTLS connection to the destination. When combined with the STRICT PeerAuthentication on the backend service, this guarantees that communication between frontend and backend will be mutually authenticated and encrypted.

The core idea is that Istio’s control plane pushes the necessary configuration to the Envoy sidecars (data plane). For mTLS, this includes the CA certificates, the workload’s own certificate and private key, and the desired security policies (PeerAuthentication and DestinationRule). The Envoy proxies then enforce these policies at runtime.

One aspect that often trips people up is the interaction between MeshPeerAuthentication and workload-specific PeerAuthentication policies. If a mesh-wide policy is set to STRICT, but a specific workload has a policy set to PERMISSIVE or DISABLED, the most restrictive policy applies to that workload. In this case, STRICT would still be enforced for traffic to that workload, but traffic from it might be allowed to use plaintext if PERMISSIVE was set for its outbound traffic. However, when enforcing mTLS between two services, both the source and destination proxies need to be configured appropriately. The PeerAuthentication on the destination controls whether it accepts mTLS, and the DestinationRule on the source controls whether it attempts to initiate mTLS.

If you set your mesh-wide PeerAuthentication to PERMISSIVE and then a specific PeerAuthentication on a service to STRICT, the STRICT policy will override the mesh-wide PERMISSIVE policy for that specific service. This allows for gradual rollout of mTLS, where you can secure individual services before enforcing it across the entire mesh.

If you’ve set all your policies to STRICT and are still seeing connections fail, the next error you’ll likely encounter is an RBAC: access denied error in the Istio proxy logs, indicating that the identity presented by the client is not authorized to access the service.

Want structured learning?

Take the full Istio course →