Mutual TLS (mTLS)
In standard TLS, only the server proves its identity to the client. In mutual TLS (mTLS) both parties present X.509 certificates, establishing cryptographic proof of identity in both directions before any application data is exchanged. This makes mTLS the strongest available mechanism for service-to-service authentication and aligns to NIST SP 800-204A, NIST SP 800-52 Rev 2, and NIST SP 800-53 Rev 5 SC-8 / IA-9.
How mTLS Works
Standard TLS performs a one-way handshake:

mTLS adds a client certificate exchange:

Both certificates are validated against a trusted CA chain before the handshake completes. A connection from a client with an untrusted, expired, or revoked certificate is rejected at the TLS layer โ before any application code runs.
When to Use mTLS
| Scenario | mTLS | Alternative |
|---|---|---|
| Service-to-service within a cluster | โ Recommended | Service mesh provides automatically |
| Internal API gateway โ backend service | โ Recommended | โ |
| Partner / B2B API integration | โ Recommended | OAuth 2.0 client credentials |
| Public consumer-facing API | Not practical | OAuth 2.0, API keys |
| IoT device authentication | โ Recommended | Device certificates |
| Legacy services that cannot present certs | Not viable | OAuth 2.0, API keys |
mTLS is the preferred pattern for zero-trust internal networking (NIST SP 800-207). Treat all internal service traffic as potentially hostile and authenticate at the transport layer regardless of network segment.
Certificate Authority Options
The critical choice in any mTLS deployment is where certificates are issued from. Running your own internal CA gives maximum control but shifts the full operational burden โ key generation, secure storage, CRL distribution, OCSP, rotation tooling, HSMs, auditing โ onto your team. Managed CA services offload this entirely and are the preferred choice.
Recommended: Managed Private CA Services
These services act as your Private CA without requiring you to operate PKI infrastructure.
AWS Private Certificate Authority (ACM PCA)
Best fit for AWS-hosted workloads.
- Issues X.509 certificates for internal services, containers, and IoT devices
- FIPS 140-2 Level 3 HSM-backed private keys
- Integrates with ACM for automatic distribution to ALB, API Gateway, and Lambda
- Integrates with cert-manager on EKS via the AWS PCA Issuer plugin
- OCSP and CRL managed by AWS
- Hierarchical: create a root CA in ACM PCA and one or more subordinate CAs per environment

Pricing: ~$400/month per active CA + $0.75 per certificate issued. Suitable at scale; expensive for experimentation.
Google Cloud Certificate Authority Service (CAS)
Best fit for GCP workloads.
- DevOps and Enterprise tiers (Enterprise for OCSP/CRL)
- CA Pools allow grouping subordinate CAs for load distribution and redundancy
- Integrates with GKE via cert-manager Google CAS issuer plugin
- IAM-controlled certificate issuance
- Certificate templates enforce Subject fields and key usages
HashiCorp Vault PKI Secrets Engine
Best fit for multi-cloud or on-premises environments. Vault acts as an intermediate CA under an external root (which can be ACM PCA, DigiCert, or a hardware root).
- Issues short-lived certificates (hours to days) โ reduces revocation risk
- ACME v2 protocol support (Vault 1.14+) for automatic renewal
- Integrates with cert-manager via the Vault Issuer
- Audit log of every certificate issued
- Can rotate the intermediate CA automatically
This is the recommended pattern for greenfield multi-cloud deployments:

Smallstep Certificate Manager (step-ca)
Open-source, ACME-native, cloud-agnostic.
- Supports ACME (automated issuance), JWK, OAuth/OIDC, and mTLS attestation
- Designed for infrastructure: Kubernetes, VMs, SSH, SPIFFE/SPIRE
stepCLI makes testing and bootstrapping straightforward- Self-hosted but operationally simple compared to a full internal PKI
DigiCert / Entrust (Commercial CAs)
Traditional enterprise CAs with full managed PKI services.
- Long-standing compliance pedigree (WebTrust, ETSI)
- Managed private CA hierarchies with SLA-backed OCSP
- Certificate Lifecycle Manager (DigiCert) or PKIaaS (Entrust) platforms handle issuance, renewal, and revocation
- Higher cost; appropriate when a commercial vendor relationship and SLAs are required
When an Internal CA is Unavoidable
Some environments โ air-gapped networks, classified systems, highly regulated sectors โ cannot use external managed services. In these cases:
- Use CFSSL (CloudFlare) or step-ca as the CA software
- Store root CA private keys in a hardware security module (HSM) โ never in software
- Restrict root CA to offline operation; use online subordinate CAs for daily issuance
- Implement CRL distribution and OCSP to support revocation
- Automate certificate issuance via cert-manager or Vault โ manual issuance does not scale
Setting Up mTLS: Step-by-Step
Step 1 โ Establish Your CA Hierarchy
Using AWS ACM PCA as the example:
# 1. Create a root CA (do this once per organisation/environment boundary)
aws acm-pca create-certificate-authority \
--certificate-authority-configuration \
'KeyAlgorithm=RSA_2048,
SigningAlgorithm=SHA256WITHRSA,
Subject={Country=US,Organisation=Acme Corp,CommonName=Acme Internal Root CA}' \
--certificate-authority-type ROOT
# 2. Create a subordinate CA per environment (production, staging)
aws acm-pca create-certificate-authority \
--certificate-authority-configuration \
'KeyAlgorithm=RSA_2048,
SigningAlgorithm=SHA256WITHRSA,
Subject={Country=US,Organisation=Acme Corp,CommonName=Acme Production Intermediate CA}' \
--certificate-authority-type SUBORDINATE
# 3. Sign the subordinate CA with the root
aws acm-pca issue-certificate \
--certificate-authority-arn <root-ca-arn> \
--csr fileb://subordinate-csr.pem \
--signing-algorithm SHA256WITHRSA \
--template-arn arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen0/V1 \
--validity Value=3,Type=YEARS
Step 2 โ Issue Service Certificates
Each service needs a certificate with a Subject Alternative Name (SAN) identifying it. Use your service's DNS name or SPIFFE URI.
# cert-manager Certificate resource (Kubernetes)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: payment-service-cert
namespace: production
spec:
secretName: payment-service-tls
duration: 24h # Short-lived: renew daily
renewBefore: 4h
subject:
organizations: ["Acme Corp"]
commonName: payment-service.production.svc.cluster.local
dnsNames:
- payment-service.production.svc.cluster.local
- payment-service.production.svc
uris:
- spiffe://cluster.local/ns/production/sa/payment-service
issuerRef:
name: aws-pca-issuer
kind: AWSPCAClusterIssuer
Certificate lifetimes: Keep leaf certificates short-lived (24โ72 hours). Short lifetimes eliminate the operational need for OCSP checks on leaf certs and mean a compromised cert self-heals quickly.
Step 3 โ Configure Services to Present and Verify Certificates
nginx (reverse proxy / sidecar)
server {
listen 443 ssl;
server_name payment-service.internal;
# Server certificate
ssl_certificate /etc/certs/server.crt;
ssl_certificate_key /etc/certs/server.key;
# TLS hardening (NIST SP 800-52 Rev 2)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
# Client certificate verification (mTLS)
ssl_client_certificate /etc/certs/ca-chain.crt; # Trusted CA bundle
ssl_verify_client on;
ssl_verify_depth 2; # Root + intermediate
location / {
# Optionally forward client cert details to the application
proxy_set_header X-Client-Cert-Subject $ssl_client_s_dn;
proxy_set_header X-Client-Cert-Serial $ssl_client_serial;
proxy_pass http://localhost:8080;
}
}
Envoy Proxy (service mesh sidecar pattern)
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
require_client_certificate: true
common_tls_context:
tls_certificates:
- certificate_chain: { filename: "/etc/certs/server.crt" }
private_key: { filename: "/etc/certs/server.key" }
validation_context:
trusted_ca:
filename: "/etc/certs/ca-chain.crt"
# Optionally restrict to a specific SPIFFE identity
match_typed_subject_alt_names:
- san_type: URI
matcher:
prefix: "spiffe://cluster.local/ns/production/"
AWS API Gateway (mTLS for REST APIs)
# 1. Upload your CA truststore to S3
aws s3 cp ca-chain.pem s3://my-bucket/truststore.pem
# 2. Create or update the API with mTLS enabled
aws apigatewayv2 create-domain-name \
--domain-name api.example.com \
--mutual-tls-authentication \
truststoreUri=s3://my-bucket/truststore.pem
# API Gateway validates the client cert against the truststore;
# the backend Lambda or integration receives $context.identity.clientCert
Service Mesh: Automatic mTLS
The operationally simplest approach to mTLS at scale is a service mesh, which injects a sidecar proxy into every pod and handles certificate issuance, distribution, rotation, and mTLS enforcement transparently.
| Mesh | Default mTLS | CA Backend |
|---|---|---|
| Istio | Permissive (opt-in strict per namespace) | Built-in istiod CA; supports Vault, AWS PCA, cert-manager |
| Linkerd | Automatic and strict for all meshed services | Built-in identity service (rotates every 24h) |
| Consul Connect | Opt-in per service | Built-in; supports Vault |
| AWS App Mesh with ACM PCA | Configured per virtual node | AWS ACM PCA |
Istio: Enforce Strict mTLS Cluster-Wide
# Reject all non-mTLS traffic across the mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system # Applies globally
spec:
mtls:
mode: STRICT
# Additionally restrict which services can call the payment service
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-service-policy
namespace: production
spec:
selector:
matchLabels:
app: payment-service
rules:
- from:
- source:
principals:
- cluster.local/ns/production/sa/order-service
- cluster.local/ns/production/sa/api-gateway
Certificate Lifecycle Management
The largest operational risk with mTLS is certificate expiry causing outages. Automate every stage of the lifecycle.
Issuance
- Use cert-manager, Vault Agent, or platform-native tooling โ never issue certificates manually
- Embed certificate requests in deployment pipelines (CI/CD issues certs at deploy time)
Rotation
- Target certificate lifetimes of 24โ72 hours for leaf certs
- cert-manager renews when
renewBeforethreshold is reached - Vault Agent with
auto_authre-issues certificates on a sidecar before expiry - For longer-lived certs (>7 days), configure OCSP or CRL checking
Revocation
| Mechanism | Latency | Operational Cost |
|---|---|---|
| Short certificate lifetime | Immediate (at expiry) | Low โ no CRL/OCSP needed |
| OCSP stapling | Near real-time | Moderate โ server caches OCSP response |
| CRL distribution | Minutes to hours | Low โ simple file distribution |
| OCSP Must-Staple | Near real-time | Low for client; server must fetch regularly |
For leaf certificates with lifetimes โค 24 hours, formal revocation is largely unnecessary โ the cert expires before revocation infrastructure can add meaningful protection.
Monitoring
Alert on:
- Certificates expiring within 14 days (warning) and 3 days (critical)
- Certificate issuance failures (rotation pipeline broken)
- mTLS handshake failure rate spikes (potential misconfiguration or compromised cert)
- Certificate serial numbers not matching expected rotation schedule
# Check expiry of a running service's certificate
openssl s_client -connect payment-service.internal:443 \
-cert client.crt -key client.key \
-CAfile ca-chain.crt </dev/null 2>/dev/null \
| openssl x509 -noout -dates
SPIFFE / SPIRE: Workload Identity at Scale
SPIFFE (Secure Production Identity Framework for Everyone) standardises workload identity for mTLS in dynamic environments where IP addresses and hostnames are ephemeral.
- Each workload receives a SPIFFE Verifiable Identity Document (SVID) โ an X.509 certificate with a URI SAN:
spiffe://trust-domain/path - SPIRE is the reference SPIFFE implementation; integrates with Vault, AWS, GCP, and Kubernetes
- Istio and Linkerd issue SPIFFE-compatible SVIDs natively
SPIFFE is recommended for Kubernetes and containerised environments where pod IPs change on every restart.
Common Pitfalls
| Mistake | Impact | Fix |
|---|---|---|
| Long certificate lifetimes (1โ2 years) | Wide window for compromised cert abuse | Use 24โ72 hour leaf certs with automated rotation |
| Shared certificates across services | Compromise of one service exposes others | Issue unique certs per service identity |
| Permissive mTLS mode left on indefinitely | Plaintext traffic accepted; mTLS provides no protection | Enforce STRICT mode after migration is complete |
| Manual certificate management | Rotation missed; outages at expiry | Automate with cert-manager or Vault Agent |
| No OCSP/CRL for longer-lived certs | Revoked certs continue to be accepted | Implement OCSP stapling or switch to short lifetimes |
| Wide CA trust bundle | Any cert from the CA is accepted | Use separate subordinate CAs per environment; restrict SANs at the policy level |
| Not validating SAN / SPIFFE URI | Any cert from the CA accepted for any service | Validate the specific service identity in the SAN |
| Bundling the CA private key with the application | Key compromise if container is extracted | CA keys must never leave the CA service (ACM PCA, Vault) |
mTLS Security Checklist
| Control | Priority | NIST Reference |
|---|---|---|
| Use a managed CA service (ACM PCA, Vault, CAS) | Critical | SC-17 |
| Root CA private key stored in HSM | Critical | SC-12 |
| Separate subordinate CAs per environment | High | SC-17 |
| Leaf certificate lifetime โค 72 hours | High | SC-12 |
| Certificate issuance and rotation fully automated | Critical | SC-12 |
| STRICT mTLS enforced โ no plaintext fallback | Critical | SC-8 |
| SAN / SPIFFE URI validated, not just CA trust | High | IA-9 |
| Unique certificate per service identity | High | IA-3 |
| TLS 1.2 minimum; TLS 1.3 preferred | Critical | SC-8, SP 800-52 |
| Certificate expiry monitoring and alerting | High | SI-5 |
| mTLS handshake failures logged and alerted | High | AU-12 |
| Revocation mechanism for certs > 72 hours | High | SC-17 |
| Authorisation policy enforced beyond mTLS (SPIFFE URI check) | High | AC-3, IA-9 |
| mTLS enforced on all internal service-to-service calls | High | SC-8, SP 800-204A |
Regulatory & Standards Reference
| Standard | Relevance |
|---|---|
| NIST SP 800-204A | Service mesh mTLS architecture for microservices |
| NIST SP 800-52 Rev 2 | TLS implementation guidelines; cipher suite and protocol requirements |
| NIST SP 800-53 Rev 5 SC-8 | Transmission confidentiality and integrity |
| NIST SP 800-53 Rev 5 SC-12 | Cryptographic key establishment and management |
| NIST SP 800-53 Rev 5 SC-17 | Public Key Infrastructure certificates |
| NIST SP 800-53 Rev 5 IA-3 | Device identification and authentication |
| NIST SP 800-53 Rev 5 IA-9 | Service identification and authentication |
| NIST SP 800-207 | Zero Trust Architecture โ mTLS as a core control |
| NIST SP 800-57 Part 1 | Key management: key lengths, lifetimes, and storage |
| FIPS 140-2 / 140-3 | HSM requirements for CA key protection |
| RFC 8705 | OAuth 2.0 Mutual-TLS Client Authentication (mTLS-bound tokens) |
| SPIFFE / SVID Specification | Workload identity standard for mTLS in dynamic environments |