S
Home Application Security ยท Updated 2026-07-10

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:

Standard TLS handshake sequence diagram

mTLS adds a client certificate exchange:

mTLS handshake sequence diagram

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.

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

AWS ACM PCA certificate authority hierarchy

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:

HashiCorp Vault certificate authority hierarchy

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
  • step CLI 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 renewBefore threshold is reached
  • Vault Agent with auto_auth re-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
The Security Architecture Site โ€” for internal reference use. Back to contents