# Issue a client certificate for mutual TLS

To issue a client certificate, generate a key and CSR as usual, then sign it with your private CA using an extensions file containing `extendedKeyUsage = clientAuth` and `basicConstraints = critical,CA:FALSE`. The `clientAuth` purpose is what makes it usable for mutual TLS — a certificate carrying only `serverAuth` is rejected by any server that checks, with an "unsupported certificate purpose" error. Use a private CA dedicated to client certificates, never a public one.

Source: https://certguru.io/openssl/create-mtls-client-certificate/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

## Step 1 — key and CSR

*The subject names the client, not a hostname*

```bash
openssl req -new -newkey rsa:2048 -nodes \
  -keyout client.key -out client.csr \
  -subj "/O=Example Inc/OU=Services/CN=payments-worker"
```

## Step 2 — the extensions that matter

*client-ext.cnf*

```ini
basicConstraints       = critical,CA:FALSE
keyUsage               = critical,digitalSignature,keyEncipherment
extendedKeyUsage       = clientAuth
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
```

| Placeholder | What to put there |
| --- | --- |
| `extendedKeyUsage = clientAuth` | The flag that makes this usable for mutual TLS. Without it, servers reject the certificate. |
| `basicConstraints = CA:FALSE` | This certificate may not sign others. |
| `keyUsage` | Restricts the key to signing and key exchange. |

## Step 3 — sign it with your client CA

```bash
openssl x509 -req -in client.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out client.crt -days 90 -sha256 \
  -extfile client-ext.cnf
```

> **Do not use -copy_extensions here**
>
> It would copy whatever the requester put in their CSR, including a `basicConstraints` claiming to
>   be a CA. For client certificates in particular — where the CSR often comes from someone else —
>   define the extensions yourself.

## Step 4 — check the purpose took effect

This is the check worth building into issuance, because the failure is silent until a handshake
fails:

```bash
openssl x509 -in client.crt -noout -ext extendedKeyUsage -ext basicConstraints
```

Expected output:

```
X509v3 Extended Key Usage:
    TLS Web Client Authentication
X509v3 Basic Constraints: critical
    CA:FALSE
```

*Does it chain to the CA?*

```bash
openssl verify -CAfile ca.crt client.crt
```

Expected output:

```
client.crt: OK
```

## Step 5 — package it for the client

PEM works for curl and OpenSSL. Browsers, Java and Windows want PKCS#12:

```bash
openssl pkcs12 -export \
  -in client.crt -inkey client.key -certfile ca.crt \
  -name "payments-worker" -out client.p12
```

> **A client certificate is a credential**
>
> The `.p12` and its password together are exactly as sensitive as a password would be. Send them by
>   different routes, and treat a leaked one as requiring revocation rather than merely rotation.

## Testing against a server

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

```bash
curl --cert client.crt --key client.key https://example.com/
```

For the server-side configuration, see [mutual TLS](/concepts/mtls/).


## Frequently asked questions

### Does a client certificate need a hostname?

No. It identifies a client, not a server, so the subject is normally a service or machine name. Subject alternative names are optional and only matter if something on your side matches against them.

### Can one certificate serve as both server and client?

Yes, if its extended key usage lists both `serverAuth` and `clientAuth`. That is common for service-to-service traffic where each side both calls and is called.

### How long should a client certificate last?

Shorter than a server certificate. Revocation is the hard part of mTLS, and a short lifetime limits how long a leaked client credential stays usable. Ninety days is a reasonable starting point if issuance is automated.

### What format does the client need?

It depends on the client. curl and OpenSSL take PEM. Browsers, Java and Windows want a PKCS#12 bundle, which is why the last step below produces one.


## References

- [openssl-req(1)](https://docs.openssl.org/3.5/man1/openssl-req/) — generating the client request
- [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/concepts/mtls/
- https://certguru.io/openssl/create-root-ca/
- https://certguru.io/openssl/sign-csr-with-ca/
- https://certguru.io/concepts/key-usage-vs-eku/
- https://certguru.io/convert/pem-to-pfx/

--
CertGuru — https://certguru.io
