# 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.

Source: https://certguru.io/concepts/mtls/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

## 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.

```text
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

1. **A CA for client certificates.** Private, and separate from whatever issues your server
   certificates. See [create a root CA](/openssl/create-root-ca/).
2. **A client certificate and key**, issued by that CA, carrying `clientAuth` in its extended key
   usage.
3. **Server configuration** telling it to request client certificates and which CA to trust for
   them.

> **Never trust a public CA for client certificates**
>
> Configuring a public CA as your client trust anchor means any certificate that CA has issued — to
>   anyone, for any domain — becomes a valid credential for your service. The client CA must be one
>   you control and use for nothing else.

## Server configuration

#### nginx

```nginx
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;
```

#### Apache

```apache
SSLCACertificateFile /etc/ssl/certs/client-ca.crt
SSLVerifyClient      require
SSLVerifyDepth       2
SSLOptions           +StdEnvVars +ExportCertData
```

#### HAProxy

```bash
bind :443 ssl crt /etc/haproxy/certs/site.pem \
     ca-file /etc/haproxy/certs/client-ca.crt verify required
```

## Testing it

*With a client certificate*

```bash
openssl s_client -connect example.com:443 -servername example.com \
  -cert client.crt -key client.key </dev/null
```

*curl equivalent*

```bash
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:

```bash
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](/concepts/key-usage-vs-eku/), and
[issuing a client certificate](/openssl/create-mtls-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](https://www.rfc-editor.org/rfc/rfc8446) — how client certificates are requested and presented
- [RFC 5280: X.509 and CRL profile](https://www.rfc-editor.org/rfc/rfc5280) — the specification every field on a certificate comes from

## Related pages

- https://certguru.io/openssl/create-mtls-client-certificate/
- https://certguru.io/openssl/create-root-ca/
- https://certguru.io/concepts/key-usage-vs-eku/
- https://certguru.io/openssl/sign-csr-with-ca/
- https://certguru.io/concepts/certificate-chain/

--
CertGuru — https://certguru.io
