curl mTLS: Configure Mutual TLS from the Terminal
Mutual TLS has both client and server present certificates. With curl, use --cert, --key, and --cacert. Here is the full setup: generate certs, call an mTLS endpoint, and fix the three failures everyone hits.
Mutual TLS (mTLS) authenticates both ends of a connection: the server presents its certificate as usual, and the client presents one too. With curl you supply the client certificate and key with --cert and --key, and trust the server's CA with --cacert. This guide generates test certificates, makes a working mTLS request, and resolves the three errors people hit most: an untrusted server CA, a missing or wrong client certificate, and a hostname mismatch.
One-way TLS vs mutual TLS
Regular HTTPS authenticates one side: the server proves its identity to the client, and the client stays anonymous at the TLS layer. Mutual TLS closes the loop — the client presents a certificate too, and the server validates it before any request is processed. The hero diagram above shows the exchange: both ends present a certificate, both verify the other, and only then is the connection trusted. It's the default pattern for service-to-service traffic and machine identity, where "who is calling this API" has to be answered by cryptography, not a bearer token that can leak.
The three curl flags
curl https://api.example.com/health \
--cert client.crt \ # the client certificate you present
--key client.key \ # its matching private key
--cacert ca.crt # the CA that signed the server's certificate--cert/--key— your client identity (the client-side half of mTLS).--cacert— the trust anchor for the server's certificate.
If the client cert and key are bundled in one PEM, --cert client.pem alone works; for a PKCS#12 bundle use --cert client.p12:password --cert-type P12.
Generate test certificates
To try mTLS locally you need a CA, a server cert, and a client cert — all chaining to that CA:
# 1. a throwaway CA
openssl genrsa -out ca.key 2048
openssl req -x509 -new -key ca.key -days 365 -out ca.crt -subj "/CN=demo-ca"
# 2. a client key + certificate signed by the CA
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=test-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt -days 90Point your server at ca.crt as the client-CA trust store (in nginx: ssl_client_certificate ca.crt; ssl_verify_client on;), and the request above will succeed.
Verify it works
# a 200 means the server accepted your client certificate
curl -sw "%{http_code}\n" -o /dev/null https://api.example.com/health \
--cert client.crt --key client.key --cacert ca.crtThe three failures everyone hits
unable to get local issuer certificate— curl can't verify the server. Pass the server's CA with--cacert. Don't reach for-k/--insecureoutside throwaway local tests; it disables verification entirely.400 No required SSL certificate was sent/alert certificate required— the server wanted a client certificate and didn't get a valid one. Confirm--cert/--keyare set and the client cert is signed by the CA the server trusts.SSL: no alternative certificate subject name matches— hostname mismatch. The name in the URL must match the server certificate's SAN. Fix the SAN or the URL; don't paper over it with--insecure.
mTLS setup checklist
- Client presents
--cert+ matching--key; both readable, key permissions locked down (chmod 600) - Client trusts the server CA via
--cacert— never-k/--insecureagainst a real endpoint - Server has
ssl_verify_client onand trusts the client CA - Server and client certificates share (or chain to) a CA each side trusts
- Certificate SANs match the hostnames actually used
- Client certificates are short-lived and rotated — the same 47-day pressure applies to machine identities
Once the terminal call works, the same certificates drive mTLS in nginx, Envoy, and a service mesh — the handshake is identical; only the configuration surface changes.
Frequently asked
What do curl's --cert and --key do?
--cert points to the client certificate curl presents to the server, and --key points to the matching private key. Together they let the client prove its identity — the client-side half of mutual TLS. --cacert tells curl which CA to trust for the server's certificate.
How do I generate a client certificate for testing mTLS?
Create a CA, then issue a client key + certificate signed by it: openssl genrsa -out client.key 2048; openssl req -new -key client.key -out client.csr -subj '/CN=test-client'; openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 90. The server must be configured to trust the same CA.
Why do I get 'SSL certificate problem: unable to get local issuer certificate'?
curl can't build a trust chain to the server's certificate. Pass the server's CA with --cacert ca.crt. Only use -k / --insecure for throwaway local testing — it disables verification entirely and must never be used against real endpoints.
Is mTLS different from normal HTTPS?
Normal HTTPS (one-way TLS) authenticates only the server to the client. mTLS adds the reverse: the client also presents a certificate the server validates, so both ends prove identity before any data flows. It's the common pattern for service-to-service and machine identity.
- RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3, IETF (client authentication).
- RFC 5280 — Internet X.509 Public Key Infrastructure Certificate and CRL Profile, IETF.