---
title: mTLS and Mutual Authentication
url: https://doc.liz6.com/en/networking/05-tls-and-pki/04-mtls-and-mutual-authentication
locale: en
area: networking
tags:
- networking
- tls-and-pki
date: 2026-06-30
modified: 2026-07-16
description: Server identity is proven via TLS certificates, while client identity is proven via mTLS—both parties present and verify certificates. In zero-trust architectures, mTLS serves as the identity foundation for service-to-service communication, but the operational cost of certificate distribution and rotation is its main bottleneck.
---

# mTLS and Mutual Authentication

> Server identity is proven via TLS certificates, while client identity is proven via mTLS—both parties present and verify certificates. In zero-trust architectures, mTLS serves as the identity foundation for service-to-service communication, but the operational cost of certificate distribution and rotation is its main bottleneck.

## Overview

Standard TLS only verifies the server's identity. mTLS (mutual TLS) adds client certificate verification: the server sends a `CertificateRequest` during the handshake, and the client must provide a certificate. This achieves the cornerstone of a "zero-trust" network—both sides mutually prove their identities without relying on IP addresses or network location. SPIFFE standardizes service identity, and Istio/Linkerd use mTLS to automatically encrypt communications for every pod in a service mesh. Running a private CA is a prerequisite for mTLS—you need full control over certificate issuance and revocation.

## TLS vs mTLS: Handshake Differences

<svg viewBox="0 0 720 400" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Comparison of TLS and mTLS handshake differences">
  <defs><marker id="mtls-arr1" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">TLS vs mTLS: From One-Way Trust to Mutual Verification</text>

  <text x="44" y="70" font-size="12" font-weight="700" fill="#64748b">TLS</text>
  <rect x="80" y="48" width="150" height="36" rx="6" fill="#e2e8f0"/>
  <text x="155" y="70" text-anchor="middle" font-size="11" fill="#334155">Server presents certificate</text>
  <line x1="230" y1="66" x2="266" y2="66" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <rect x="270" y="48" width="160" height="36" rx="6" fill="#e2e8f0"/>
  <text x="350" y="70" text-anchor="middle" font-size="11" fill="#334155">Client verifies Server</text>
  <line x1="430" y1="66" x2="466" y2="66" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <text x="474" y="70" font-size="12" font-weight="700" fill="#475569">→ One-way trust</text>

  <text x="44" y="122" font-size="12" font-weight="700" fill="#3730a3">mTLS</text>
  <rect x="80" y="100" width="225" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="192" y="122" text-anchor="middle" font-size="10.5" fill="#3730a3">Server cert + CertificateRequest</text>
  <line x1="305" y1="118" x2="341" y2="118" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <rect x="345" y="100" width="175" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="432" y="122" text-anchor="middle" font-size="11" fill="#3730a3">Client must also present cert</text>
  <line x1="520" y1="118" x2="556" y2="118" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <text x="564" y="122" font-size="12" font-weight="700" fill="#0f766e">→ Mutual verification</text>

  <text x="360" y="170" text-anchor="middle" font-size="13" font-weight="700" fill="#1f2933">TLS 1.3 mTLS Simplified Handshake</text>

  <rect x="60" y="182" width="600" height="32" rx="6" fill="#f1f5f9"/>
  <text x="360" y="203" text-anchor="middle" font-size="11.5" fill="#334155">Client → Server: ClientHello + key_share</text>
  <line x1="360" y1="214" x2="360" y2="228" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>

  <rect x="60" y="230" width="600" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="248" text-anchor="middle" font-size="11" fill="#3730a3">Server → Client: ServerHello + key_share +</text>
  <text x="360" y="262" text-anchor="middle" font-size="11" fill="#3730a3">{EncryptedExtensions + CertificateRequest + ...}</text>
  <line x1="360" y1="268" x2="360" y2="282" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>

  <rect x="60" y="284" width="600" height="36" rx="6" fill="#ccfbf1" stroke="#99f6e4"/>
  <text x="360" y="306" text-anchor="middle" font-size="11.5" font-weight="700" fill="#115e59">Client → Server: {Certificate + CertificateVerify + Finished}</text>

  <text x="360" y="332" text-anchor="middle" font-size="11" fill="#0f766e">↑ Extra compared to standard TLS 1.3: Certificate + CertificateVerify (provided by client)</text>

  <rect x="40" y="338" width="640" height="46" rx="7" fill="#eef2ff"/>
  <text x="360" y="357" text-anchor="middle" font-size="11" fill="#3730a3">CertificateRequest carries: certificate_authorities (empty = any CA)</text>
  <text x="360" y="373" text-anchor="middle" font-size="11" fill="#3730a3">signature_algorithms · certificate_extensions (e.g., SPIFFE OID)</text>
</svg>

## Private CA Management

The first step in deploying mTLS: establish a private CA.

```bash
# 1. Create Root CA (offline, hardware security module or air-gapped machine):
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ca.key
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
  -subj "/CN=Private CA" -extensions v3_ca

# 2. Issue server/client certificates:
openssl genpkey -algorithm EC -out server.key
openssl req -new -key server.key -out server.csr \
  -subj "/CN=metrics-agent-001"
openssl x509 -req -days 90 -in server.csr -CA ca.crt -CAkey ca.key \
  -out server.crt -set_serial 0x01

# 3. Revoke (if a certificate is compromised):
openssl ca -revoke server.crt -keyfile ca.key -cert ca.crt
```

## SPIFFE

A standardized service identity framework that carries SPIFFE IDs in the CN/SAN fields of mTLS certificates:

```
SPIFFE ID: spiffe://trust-domain/path
  trust-domain: Organization/cluster identifier
  path: Service/instance identifier

Example: spiffe://liz6.com/metrics-agent/li-cn-node-2

In X.509 certificates:
  SAN URI: spiffe://liz6.com/metrics-agent/li-cn-node-2
  → Can be verified via URI SAN (not just CN)

SVID (SPIFFE Verifiable Identity Document):
  = X.509 cert (short-lived: default 1 hour) + private key
  → Automatically rotated by SPIRE agent
```

## Service Mesh (Istio/Linkerd)

<svg viewBox="0 0 720 270" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Sidecars in Service Mesh automatically handle mTLS">
  <defs><marker id="mtls-arr2" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="270" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">mTLS in Service Mesh: Application Code is Unaware</text>

  <rect x="220" y="50" width="280" height="34" rx="6" fill="#4f46e5"/>
  <text x="360" y="71" text-anchor="middle" font-size="10.5" font-weight="700" fill="#ffffff">control plane (istiod / linkerd-identity)</text>
  <line x1="290" y1="84" x2="210" y2="132" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>
  <line x1="430" y1="84" x2="470" y2="132" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>
  <text x="360" y="108" text-anchor="middle" font-size="10" fill="#64748b">Issue short-lived certs · Auto-rotation</text>

  <rect x="55" y="138" width="80" height="40" rx="6" fill="#e2e8f0"/>
  <text x="95" y="162" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">Pod A</text>
  <line x1="135" y1="158" x2="153" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="155" y="134" width="150" height="48" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="230" y="154" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">sidecar proxy</text>
  <text x="230" y="169" text-anchor="middle" font-size="10" fill="#4f46e5">Envoy / linkerd-proxy</text>
  <line x1="305" y1="158" x2="323" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="325" y="143" width="70" height="30" rx="15" fill="#0d9488"/>
  <text x="360" y="163" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">mTLS</text>
  <line x1="395" y1="158" x2="413" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="415" y="134" width="150" height="48" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="490" y="154" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">sidecar proxy</text>
  <text x="490" y="169" text-anchor="middle" font-size="10" fill="#4f46e5">Envoy / linkerd-proxy</text>
  <line x1="565" y1="158" x2="583" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="585" y="138" width="80" height="40" rx="6" fill="#e2e8f0"/>
  <text x="625" y="162" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">Pod B</text>

  <rect x="40" y="200" width="640" height="50" rx="8" fill="#f0fdfa"/>
  <text x="360" y="220" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">Application code does not need to know about mTLS</text>
  <text x="360" y="238" text-anchor="middle" font-size="11" fill="#0f766e">Each sidecar obtains short-lived certificates from the control plane, with automatic rotation and automatic mTLS</text>
</svg>

## Comparison

| | mTLS | API Key | JWT (OAuth2) |
|---|---|---|---|
| Transport Layer | L4 (TLS handshake phase) | L7 (HTTP header) | L7 (HTTP header) |
| Identity Binding | Certificate (CN/SAN + expiry) | Key string | Claims (sub, exp, aud) |
| Rotation | CA re-issues | Generate new key | Short TTL + refresh |
| Revocation | CRL / OCSP | Revoke in DB | Short TTL (natural expiry) |
| Middleware Overhead | Low (TLS itself) | Medium (verify key) | Medium (verify JWT) |
| Zero-Trust Compatible | ✓ (SPIFFE) | ✗ | △ (Requires PKI for token signing) |

## References

- **RFC**: 8446, 8705
- **SPIFFE**: spiffe.io, github.com/spiffe/spire
- **Istio**: istio.io/latest/docs/concepts/security/#mutual-tls-authentication

*Keywords: mTLS, client certificate, SPIFFE, SPIRE, service mesh, private CA, certificate revocation, SVID*
