Mutual TLS (mTLS)

Overview

Mutual TLS (mTLS) extends ordinary TLS, where only the server is verified by certificate, so that the client also presents its own X.509 certificate and both sides verify each other. Authentication happens at the transport layer (the TLS handshake), not the application layer, and it provides strong identity assurance for service-to-service traffic, zero-trust networks, and finance/IoT.

Transport

TLS client certificate (transport layer, presented during the TLS handshake)

Details

During the TLS handshake the server sends a CertificateRequest; the client responds with its certificate plus a CertificateVerify (signed with its private key) to prove it owns that certificate. The server checks that the certificate is signed by a trusted CA, is not expired or revoked (CRL/OCSP), and that its Subject/SAN matches what is expected. No password or token is exchanged; authentication completes on proof of private-key possession alone.

A common setup has a reverse proxy (nginx, Envoy, API Gateway) terminate mTLS and forward the verified client identity to the backend via headers such as `X-Client-Cert` or `X-Forwarded-Client-Cert`. On certificate error or absence, nginx rejects with statuses like 495/496.

It suits service-to-service (in-mesh) traffic, device authentication, and partner API integrations more than human login. It is strong, but the realistic trade-off is the operational burden of the PKI lifecycle: issuing, distributing, renewing, and revoking certificates.

Request / Response example

TLS handshake: server requests CertificateRequest → client sends its X.509 certificate + CertificateVerify (proves possession of the private key)

In code

curl --cert client.crt --key client.key --cacert ca.crt \
  https://api.example.com/internal
const https = require('https');
https.request({
  host: 'api.example.com', port: 443, path: '/internal',
  cert: fs.readFileSync('client.crt'),
  key:  fs.readFileSync('client.key'),
  ca:   fs.readFileSync('ca.crt')
}, res => { /* ... */ }).end();
ssl_client_certificate /etc/nginx/ca.crt;
ssl_verify_client on;   # require a valid client certificate
# reject with 495/496 on cert error/absence

Security considerations

Related headers

X-Client-CertX-SSL-Client-VerifyX-Forwarded-Client-Cert

Related status codes