Mutual TLS (mTLS) protects private APIs, admin endpoints, and service-to-service traffic by making the client prove its identity during the TLS handshake. A PKCS#12 archive, usually saved as .p12 or .pfx, bundles the client certificate and private key into one password-protected file that cURL can present to the server.
In cURL, --cert points to the client identity, --cert-type P12 tells cURL to treat that file as PKCS#12 instead of PEM, and --cacert verifies the server certificate against the issuing certificate authority. Without the CA file or another trusted CA source, the server can still fail verification even when the client certificate is correct.
The exact workflow depends on the TLS backend under cURL. Current upstream file-based support includes OpenSSL, and it adds GnuTLS PKCS#12 support from curl 8.11.0 onward, while older macOS Secure Transport builds can also load PKCS#12 archives directly. If the backend is Schannel on Windows, import the .p12 or .pfx archive into the certificate store first and reference the store path with --cert instead of using the file-based command shown below. Putting the PKCS#12 password directly on the command line is useful for a quick proof, but it exposes the secret to shell history and process inspection on many systems.
Steps to use PKCS#12 client certificates with cURL:
- Check which TLS backend your cURL build uses before choosing the PKCS#12 loading path.
$ curl --version curl 8.7.1 (x86_64-apple-darwin25.0) libcurl/8.7.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.68.0 Release-Date: 2024-03-27 Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp Features: alt-svc AsynchDNS GSS-API HSTS HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz MultiSSL NTLM SPNEGO SSL threadsafe UnixSockets
Use the file-based PKCS#12 command in this article on OpenSSL, GnuTLS 8.11.0+, and older macOS Secure Transport builds that read .p12 archives directly.
If this line shows Schannel, import the archive into the Windows certificate store first and use a store path such as CurrentUser\MY\934a7ac6f8a5d579285a74fa61e19f23ddfe8d7a with --cert. The file-based PKCS#12 example below is not the Schannel path.
- Restrict the PKCS#12 archive and CA file so other local users cannot read them.
$ chmod 600 ~/pki/payments-api-client.p12 ~/pki/corp-root-ca.pem
A readable PKCS#12 archive exposes the same private key that authenticates the client to the protected service.
- Send the request with the PKCS#12 archive, its password, and the CA file that verifies the server certificate.
$ curl --silent --show-error \ --cacert ~/pki/corp-root-ca.pem \ --cert-type P12 \ --cert ~/pki/payments-api-client.p12:<p12-password> \ --output /dev/null \ --write-out 'verify=%{ssl_verify_result} http=%{response_code}\n' \ https://payments-api.int.example.com:8443/health verify=0 http=200On PEM-default backends, leaving out --cert-type P12 usually ends with curl: (58) because cURL tries to parse the PKCS#12 archive as a PEM certificate.
The :password suffix is exposed to shell history and process listings on many systems, so move it into a protected cURL config file or another secret source before using this pattern in automation.
- Repeat the request with verbose output when you need proof that the server requested a client certificate and the handshake completed cleanly.
$ curl --verbose --silent --show-error \ --cacert ~/pki/corp-root-ca.pem \ --cert-type P12 \ --cert ~/pki/payments-api-client.p12:<p12-password> \ https://payments-api.int.example.com:8443/health \ --output /dev/null * Host payments-api.int.example.com:8443 was resolved. * Trying 192.0.2.24:8443... ##### snipped ##### * TLSv1.3 (IN), TLS handshake, Request CERT (13): ##### snipped ##### * SSL certificate verify ok. > GET /health HTTP/1.1 ##### snipped ##### < HTTP/1.1 200 OK
Look for the server's Request CERT line, a successful certificate verification message, and the expected HTTP status before reusing the command in scripts or CI.
Related: How to debug TLS handshake with cURL
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
