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.

Updated August 19, 2026 Verified against OpenSSL 3.5.7

Step 1 — key and CSR #

The subject names the client, not a hostname
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
basicConstraints       = critical,CA:FALSE
keyUsage               = critical,digitalSignature,keyEncipherment
extendedKeyUsage       = clientAuth
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer

Replace these values

PlaceholderWhat to put there
extendedKeyUsage = clientAuthThe flag that makes this usable for mutual TLS. Without it, servers reject the certificate.
basicConstraints = CA:FALSEThis certificate may not sign others.
keyUsageRestricts the key to signing and key exchange.

Step 3 — sign it with your client CA #

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

Step 4 — check the purpose took effect #

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

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?
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:

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

Testing against a server #

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/

For the server-side configuration, see mutual TLS.

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