mTLS (Mutual TLS)

mTLS (Mutual TLS)

The Problem It Solves

Standard TLS (HTTPS) verifies that the server is who it claims to be — the client checks the server’s certificate. But the server doesn’t verify the client’s identity at the TLS level. Any client that can reach the endpoint can send requests.

For internal microservices, you need both sides to prove their identity: Service A must prove it’s Service A before Service B accepts its request. This is the authentication part of zero-trust networking.

sequenceDiagram
    participant A as Service A
(client) participant B as Service B
(server) participant CA as Certificate Authority
(internal CA) Note over CA: Issues certificates to both services A->>B: TLS ClientHello B->>A: TLS ServerHello + Server Certificate Note over A: A verifies B's certificate
against the CA (standard TLS) B->>A: CertificateRequest A->>B: Client Certificate Note over B: B verifies A's certificate
against the CA (the "mutual" part) Note over A,B: TLS handshake complete
Both identities verified
Encrypted channel established A->>B: GET /api/users/123
(over encrypted channel) B-->>A: {user: ...}

Why mTLS for Internal Services?

PropertyWhy it matters
Identity at the transport layerThe service identity is proven before any application code runs — the TLS handshake itself is the authentication
No shared secrets in application codeUnlike API keys or JWTs, there’s no token to leak in logs, environment variables, or error messages
Certificate rotation is automatedTools like cert-manager (Kubernetes), Vault, or SPIFFE/SPIRE auto-rotate certificates without deploys
Works with any protocolgRPC, HTTP, TCP — anything that runs over TLS. Protocol-agnostic
Service mesh integrationIstio/Envoy sidecar proxies handle mTLS transparently — application code is unaware

Certificate Management at Scale

flowchart LR
    subgraph "Certificate Lifecycle"
        Issue[CA Issues Cert
validity: 24 hours] --> Deploy[Deploy to Service
auto-injected by infra] Deploy --> Use[Service Uses Cert
for mTLS connections] Use --> Rotate[Auto-Rotate
before expiry] Rotate --> Issue end subgraph "Tools" V[HashiCorp Vault
PKI secrets engine] CM[cert-manager
Kubernetes] SP[SPIFFE/SPIRE
workload identity] end V & CM & SP -.-> Issue

Short-lived certificates (hours, not years) are the modern best practice. If a certificate is compromised, it expires before the attacker can exploit it. This eliminates the need for a Certificate Revocation List (CRL) or OCSP — the certificate simply stops working.

When mTLS Is Overkill

mTLS adds complexity: certificate authority setup, cert distribution, rotation automation, and debugging TLS handshake failures. For simpler internal service communication, JWT with short expiry is often sufficient and easier to operate.

Test Your Understanding

‘We already use HTTPS between services, so traffic is encrypted and authenticated.’ What does mTLS add that standard one-way TLS does not?

Client authentication. In standard TLS the client verifies the server’s certificate, but the server accepts any client that can reach it — it has no cryptographic proof of who’s calling. mTLS adds the reverse leg: the server also requests and verifies the client’s certificate against the shared CA. Now both ends prove identity before any application code runs.

Why it matters for zero-trust: Service B can enforce ‘only Service A may call me’ at the transport layer, instead of trusting a network boundary or an easily-forwarded bearer token.

A team issues mTLS certificates valid for 24 hours and skips setting up a Certificate Revocation List (CRL) / OCSP. Is skipping revocation infrastructure reckless?

No — short lifetimes are the revocation mechanism. A compromised cert is useful only until it expires, and 24 hours later it simply stops working with no action needed. That sidesteps CRL/OCSP entirely, which are notoriously painful operationally (distribution lag, soft-fail behavior, extra round-trips).

The catch: it only works if issuance and rotation are automated (Vault, cert-manager, SPIFFE/SPIRE). If rotation is manual, short lifetimes cause outages instead — the very reason long-lived certs (and thus CRLs) existed in the first place.