Mutual TLS
Mutual TLS is ordinary TLS with one addition — the client also presents a certificate, so both ends authenticate rather than just the server. The server is configured to request and require a client certificate, and to trust a specific CA for them; the client is configured with its own certificate and key. The client certificate needs `clientAuth` in its extended key usage, and the CA that issues client certificates should be a private one you control, not a public CA.
What actually changes #
An ordinary TLS handshake authenticates one side. In mTLS the server sends a CertificateRequest
and the client answers with a certificate and a signature proving it holds the matching key.
Ordinary TLS Mutual TLS
------------ ----------
Server proves identity Server proves identity
Client verifies it Client verifies it
Server requests a certificate
Client proves identity
Server verifies it against its client CA The three pieces you need #
- A CA for client certificates. Private, and separate from whatever issues your server certificates. See create a root CA.
- A client certificate and key, issued by that CA, carrying
clientAuthin its extended key usage. - Server configuration telling it to request client certificates and which CA to trust for them.
Server configuration #
ssl_client_certificate /etc/ssl/certs/client-ca.crt;
ssl_verify_client on; # 'optional' to roll out gradually
ssl_verify_depth 2;
# Pass the identity to the application.
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Verify $ssl_client_verify; SSLCACertificateFile /etc/ssl/certs/client-ca.crt
SSLVerifyClient require
SSLVerifyDepth 2
SSLOptions +StdEnvVars +ExportCertData bind :443 ssl crt /etc/haproxy/certs/site.pem \
ca-file /etc/haproxy/certs/client-ca.crt verify required Testing it #
openssl s_client -connect example.com:443 -servername example.com \
-cert client.crt -key client.key </dev/null curl --cert client.crt --key client.key https://example.com/ Without one, a server set to require closes the connection during the handshake — an abrupt
failure that does not look like an authentication error, which is worth knowing before you debug it.
What usually breaks #
| Symptom | Cause |
|---|---|
| Handshake fails with no useful message | Server requires a certificate, client sent none |
unsupported certificate purpose |
Client certificate lacks clientAuth in its EKU |
| Server rejects a valid-looking certificate | Issued by a CA the server does not trust for clients |
| Works with curl, fails from the application | The runtime uses a different key or trust store |
| Fails after renewal | New client certificate issued by a different CA |
Check the extended key usage first — it is the most common single cause:
openssl x509 -in certificate.crt -noout -ext extendedKeyUsage Expected output
X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication
See key usage vs extended key usage, and issuing a client certificate.
Frequently asked questions
- Can I use a public CA for client certificates?
- You should not. Trusting a public CA for client authentication means every certificate that CA has ever issued — millions of them — becomes a valid credential for your service. Use a private CA whose only purpose is issuing your client certificates.
- What is the difference between "request" and "require"?
- Requesting asks the client for a certificate and continues without one if it is not offered, which lets you roll mTLS out gradually. Requiring rejects the handshake outright. Most deployments start with request and switch to require once every client is issued.
- Does mTLS replace application-level authentication?
- It can, but it answers a different question. A certificate proves which machine or service is connecting; it says nothing about which user is acting. Many systems use both.
- How do I revoke one client?
- Publish a CRL your server checks, or re-issue the CA with a new trust list. This is the main operational cost of mTLS, and the reason client certificates are usually issued with short lifetimes.
References
- RFC 8446: TLS 1.3 — how client certificates are requested and presented
- RFC 5280: X.509 and CRL profile — the specification every field on a certificate comes from