Mutual TLS and operations

Issue a client certificate

Create a separate client identity with client-auth usage instead of reusing the server certificate.

Until now only the server proved its identity. Mutual TLS turns on the other half: the client presents a certificate too, and the server gets a certificate-backed client identity instead of a password or an API key. It’s common between backend services, on admin endpoints, and anywhere a stolen bearer token would hurt.

A client identity is its own credential. Issue it with its own key, separate from the HTTPS server. Never reuse the server certificate for a client. The two roles have different lifetimes, different holders, and different revocation stories.

Let’s create a client key and CSR:

openssl req -new -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj '/CN=lab-client'

Notice what’s missing compared to the server CSR: no DNS names, no SAN. A client certificate identifies a party, not a hostname. The common name lab-client is the identity the server will see.

Now sign it with a client-auth extension. The Extended Key Usage extension declares what a certificate may be used for. For a client it must say client authentication:

printf 'extendedKeyUsage = clientAuth\n' > client-ext.cnf
openssl x509 -req -in client.csr -CA lab-ca.crt -CAkey lab-ca.key -CAcreateserial -out client.crt -days 14 -sha256 -extfile client-ext.cnf

-extfile injects the extension during signing. Without it you get a generic certificate, and strict servers reject client certificates that lack the clientAuth usage.

Let’s inspect the Extended Key Usage before we use the certificate anywhere:

openssl x509 -in client.crt -noout -subject -ext extendedKeyUsage
# subject=CN=lab-client
# X509v3 Extended Key Usage:
#     TLS Web Client Authentication

TLS Web Client Authentication is the line that makes this a client certificate. If you see TLS Web Server Authentication instead, you signed with the wrong extension file. Reissue before touching the server.

And confirm the lab CA vouches for it:

openssl verify -CAfile lab-ca.crt client.crt
# client.crt: OK

A failure you can predict: sign without the extension, then wonder why a picky server rejects an otherwise valid certificate. Decode the EKU first, every time. It costs one command.

Treat the result like what it is. client.key authenticates whoever holds it. Protect it, scope it, rotate it, and revoke it like any credential. One certificate per client, short lifetimes, and a plan for the day a laptop carrying client.key goes missing.

Lesson completed