DevgainsDevgainsDevgains
All articles

mTLS Explained: How Mutual TLS Authenticates Services

·10 min read
mTLS Explained: How Mutual TLS Authenticates Services

Photo: Unsplash

mTLS (mutual TLS) is TLS where both sides prove their identity with a certificate — not just the server. Regular HTTPS authenticates the server to the client; mutual TLS also authenticates the client to the server, so two services can each cryptographically confirm who they are talking to before a single byte of application data flows. This is mTLS explained for engineers who build and operate distributed systems: what it actually does, how the handshake works, how to set it up, and the mistakes that quietly break it in production. It is a core building block of the Devgains security cluster and the practical mechanism behind zero trust architecture.

Quick Answer: What Is mTLS?

mTLS is a mutual authentication scheme built on top of standard TLS. In ordinary TLS, the client verifies the server's certificate and the connection is encrypted, but the server has no cryptographic proof of who the client is. In mutual TLS, the client also presents a certificate, and the server verifies it against a trusted certificate authority (CA). The result is a connection where both endpoints have a verified identity and all traffic is encrypted.

In one sentence: mTLS turns "the connection is encrypted" into "both parties are encrypted and provably who they claim to be."

Why It Matters

Inside a modern deployment, most traffic is service-to-service: an API calls an auth service, which calls a database proxy, which calls a cache. Historically those internal calls were trusted simply because they came from "inside the network." That assumption is exactly what zero trust tears down — and mTLS is how you replace it.

  • Identity, not IP. A rotating pod IP or a shared network are not identity. A certificate signed by your CA is. mTLS lets a service authorize callers by who they are, not where they connect from.
  • Encryption everywhere. mTLS encrypts internal traffic by default, closing the gap where sensitive data moves in plaintext between services because "it never leaves the VPC."
  • Blast-radius containment. If one workload is compromised, it can only present its own certificate. It cannot impersonate another service, so lateral movement is far harder.
  • Compliance and audit. "Every service-to-service call is mutually authenticated and encrypted" is a clean, provable control for auditors — much stronger than firewall rules.

mTLS complements, rather than replaces, application-layer auth like OAuth 2.0 and OIDC: mTLS authenticates the workload, while tokens authenticate the user or request riding on top of it.

How mTLS Works: The Handshake

Standard TLS and mutual TLS share the same foundation; mTLS just adds two extra steps. Here is the mutual handshake in plain terms:

  1. Client Hello. The client opens the connection and proposes cipher suites and TLS version.
  2. Server certificate. The server sends its certificate. The client validates it against its trusted CAs and checks the hostname — this is the part ordinary HTTPS already does.
  3. Certificate request. In mTLS, the server also asks the client for a certificate. This single flag is the difference between one-way TLS and mutual TLS.
  4. Client certificate. The client presents its own certificate and proves it holds the matching private key (via a signed handshake message).
  5. Server verifies the client. The server validates the client certificate against its trusted CA, checks expiry and revocation, and reads the identity (Common Name or SAN).
  6. Session established. Both sides derive symmetric session keys and encrypt all further traffic.

The trust anchor is the CA. Both sides must trust a CA that signed the other's certificate. In practice that means running an internal CA (or a service mesh that provides one) and distributing its root to every workload.

TLS vs mTLS at a Glance

AspectOne-way TLS (HTTPS)Mutual TLS (mTLS)
Server authenticatedYesYes
Client authenticatedNoYes, via certificate
EncryptionYesYes
Typical useBrowser → websiteService → service, API → API
Client identity sourceApp-layer token / cookieX.509 certificate
Certificate managementServer onlyBoth ends + a shared CA

Setting Up mTLS: A Minimal Walkthrough

You can implement mTLS manually to understand the moving parts. First, create a CA and issue a server and a client certificate from it. Real deployments automate this, but the primitives are the same.

# 1. Create a private CA (root key + self-signed root cert)
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -subj "/CN=devgains-internal-ca" -out ca.crt
 
# 2. Issue a SERVER certificate signed by the CA
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=orders.internal" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 365 -sha256 -out server.crt
 
# 3. Issue a CLIENT certificate signed by the SAME CA
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=checkout.internal" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 365 -sha256 -out client.crt

The key idea: both certificates chain to the same CA. The server will accept the client because ca.crt signed client.crt, and the client accepts the server for the same reason.

Next, tell the server to require a client certificate. In Nginx this is two directives — one to trust the CA, one to make client certs mandatory:

server {
  listen 443 ssl;
  server_name orders.internal;
 
  ssl_certificate     /etc/nginx/certs/server.crt;
  ssl_certificate_key /etc/nginx/certs/server.key;
 
  # Require and verify a client certificate signed by our CA
  ssl_client_certificate /etc/nginx/certs/ca.crt;
  ssl_verify_client on;      # <-- this line turns HTTPS into mTLS
 
  location / {
    # Pass the verified client identity to the app
    proxy_set_header X-Client-CN $ssl_client_s_dn;
    proxy_pass http://127.0.0.1:8080;
  }
}

ssl_verify_client on is the whole difference: without it you have HTTPS, with it you have mTLS. A client connecting without a valid certificate is rejected at the TLS layer, before your application code ever runs. You can verify the setup with curl, presenting the client certificate and key:

curl --cacert ca.crt \
     --cert client.crt --key client.key \
     https://orders.internal/health

Drop --cert/--key and the same request fails with a TLS handshake error — proof the server is enforcing mutual authentication.

mTLS in Service Meshes

Managing certificates by hand across hundreds of services does not scale — the real win comes from automation. A service mesh (Istio, Linkerd, Consul) issues short-lived workload certificates, rotates them automatically, and enforces mTLS through sidecar proxies, so application code stays unchanged. This is where mTLS meets Kubernetes networking: the mesh wraps every pod's traffic transparently. In Istio you flip an entire namespace to strict mTLS with one policy:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: payments
spec:
  mtls:
    mode: STRICT   # reject any plaintext; require mutual TLS on every call

STRICT mode means a pod that tries to talk to the payments namespace without a mesh-issued certificate is refused. The mesh's built-in CA (often SPIFFE-based) mints certificates that live for hours, not years, so a leaked key is useless almost immediately — a far better posture than the long-lived secrets described in secrets in environment variables.

Best Practices

  • Use short-lived certificates and automate rotation. Hours or days, not years. Automation makes short lifetimes practical and shrinks the value of any stolen key.
  • Run a dedicated internal CA. Do not reuse your public web CA for internal workloads. Keep the internal root offline or in an HSM/KMS.
  • Authorize on identity from the certificate. Read the SAN/CN and enforce which service may call which — mTLS proves identity, but you still need an authorization policy.
  • Prefer STRICT mode once migrated. PERMISSIVE (accept both plaintext and mTLS) is a migration aid, not a destination. Leaving it on defeats the point.
  • Monitor certificate expiry. Expired certs are the number-one mTLS outage. Alert well before expiry and treat rotation failures as incidents.
  • Terminate carefully. If a load balancer terminates TLS, decide whether the hop behind it also needs mTLS. "mTLS at the edge, plaintext inside" is a common false sense of security.

Common Mistakes

  • Confusing encryption with authentication. One-way TLS encrypts but does not verify the client. If your threat model includes impersonation, encryption alone is not enough.
  • Trusting the wrong CA breadth. Trusting a broad public CA for client verification means anyone with a cert from that CA is accepted. Trust a narrow, internal CA only.
  • Ignoring revocation. A compromised certificate valid for a year is a year-long backdoor. Short lifetimes are the pragmatic substitute for perfect revocation.
  • Leaking private keys. A client key in a container image or committed to git undermines the whole scheme — the same class of failure as supply-chain and dependency leaks.
  • Treating mTLS as the only auth layer. mTLS authenticates the workload. It does not know which user made the request — you still need tokens like JWTs handled correctly for user-level identity.

Takeaways

  • mTLS authenticates both ends of a connection with certificates, not just the server.
  • The one technical difference from HTTPS is that the server requests and verifies a client certificate signed by a trusted CA.
  • It replaces "trusted because it's on the network" with "trusted because its identity is proven" — the heart of zero trust.
  • Automate it: short-lived certs plus a service mesh or internal CA make mTLS operable at scale.
  • mTLS authenticates workloads; pair it with token-based auth for user identity and with policy for authorization.

FAQ

What is mTLS in simple terms? mTLS is TLS where both the client and the server show a certificate to prove who they are. Normal HTTPS only checks the server; mutual TLS checks both, so two services can be sure they are talking to the right counterpart.

What is the difference between TLS and mTLS? TLS authenticates the server and encrypts traffic. mTLS does the same and requires the client to present a certificate the server verifies. The single change is that the server asks for — and validates — the client's certificate.

Does mTLS replace OAuth or JWTs? No. mTLS authenticates the workload (which service is calling). OAuth 2.0, OIDC, and JWTs authenticate the user or request on top of that connection. Robust systems use both: mTLS for service identity, tokens for user identity.

Why use a service mesh for mTLS? A mesh issues and rotates short-lived workload certificates automatically and enforces mTLS through sidecar proxies, so you get mutual authentication for every service without changing application code or managing certificates by hand.

Is mTLS overkill for a small app? For a single public web app, one-way HTTPS is usually enough. mTLS pays off when you have multiple internal services that call each other and you want each call mutually authenticated — especially under a zero trust model.

Conclusion

mTLS is a small technical change with a large security payoff: make the server ask the client for a certificate, and suddenly every connection carries a verified identity on both ends. That shift — from trusting the network to trusting proven identity — is precisely what zero trust architecture demands, and mutual TLS is its most practical enforcement point for service-to-service traffic. Start by understanding the handshake, prove it locally with a hand-rolled CA, then let a service mesh or automated internal CA carry the operational load. For the surrounding building blocks — identity, secrets, and authorization — keep reading across the security cluster.

References

10 min read

Read next