Mutual TLS (mTLS) protects sensitive HTTPS endpoints by requiring a client certificate in addition to the usual server certificate. Command-line access through cURL allows automated jobs, CI pipelines, and troubleshooting tools to authenticate to these endpoints with the same assurance normally provided by browser-based clients.
A PKCS#12 archive, typically stored as .p12 or .pfx, bundles a private key, the corresponding X.509 client certificate, and optionally the issuing certificate chain into a single encrypted file. During the TLS handshake, cURL passes this archive to the TLS backend via the --cert and --cert-type options so the client certificate can prove possession of the private key and satisfy server-side client authentication rules.
PKCS#12 archives carry sensitive private keys and usually require a passphrase, so safe usage depends on restrictive file permissions, explicit declaration of the archive type, and minimal exposure of secrets in shell history or process listings. On Ubuntu, cURL builds linked against OpenSSL treat PEM as the default certificate format, so PKCS#12 usage benefits from an explicit --cert-type P12 flag and careful passphrase handling.
Steps to use PKCS#12 client certificates with cURL:
- Display the installed cURL version and TLS backend in a terminal on Ubuntu.
$ curl --version curl 8.7.1 (x86_64-pc-linux-gnu) libcurl/8.7.1 OpenSSL/3.0.2 zlib/1.2.13 Release-Date: 2024-03-27 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp Features: AsynchDNS HTTPS-proxy IPv6 Largefile libz NTLM SSL TLS-SRP HTTP2 HTTP3 ##### snipped #####
The line containing OpenSSL, GnuTLS, or another TLS backend indicates which library handles PKCS#12 parsing and certificate presentation.
- Create a private directory for the PKCS#12 archive under the home folder with restrictive permissions.
$ mkdir -p ~/certs $ chmod 700 ~/certs $ mv ~/Downloads/client-cert.p12 ~/certs/ $ chmod 600 ~/certs/client-cert.p12 $ ls -l ~/certs/client-cert.p12 -rw------- 1 user user 4096 Dec 7 12:00 /home/user/certs/client-cert.p12
World-readable archives such as /home/user/certs/client-cert.p12 expose the embedded private key and allow any local user with access to the file to impersonate the mTLS client.
- Observe the error produced when the PKCS#12 archive is used without declaring its type.
$ curl --cert ~/certs/client-cert.p12:'p12-password' \ --cacert ~/certs/server-ca.pem \ https://mtls.example.com/protected/ curl: (58) unable to load client cert 0 client certificate files ##### snipped #####
This error commonly appears when cURL expects a PEM-encoded certificate but receives a PKCS#12 archive instead.
- Send an mTLS request with cURL using the PKCS#12 archive while explicitly setting the certificate type.
$ curl --cert-type P12 --cert ~/certs/client-cert.p12:'p12-password' \ --cacert ~/certs/server-ca.pem \ https://mtls.example.com/protected/ {"status":"ok","message":"client certificate accepted"}Placing the PKCS#12 passphrase directly on the command line records it in shell history and can expose it in process listings on multi-user systems.
- Provide the PKCS#12 password through a shell variable instead of writing it directly on the command line.
$ read -s P12_PASS $ curl --cert-type P12 --cert ~/certs/client-cert.p12:"$P12_PASS" \ --cacert ~/certs/server-ca.pem \ https://mtls.example.com/protected/ {"status":"ok","message":"client certificate accepted"}Secrets entered with read -s are not echoed to the terminal; unsetting the variable with unset P12_PASS after use further reduces exposure.
- Verify mutual TLS authentication by repeating the request with verbose output enabled.
$ curl -v --cert-type P12 --cert ~/certs/client-cert.p12:"$P12_PASS" \ --cacert ~/certs/server-ca.pem \ https://mtls.example.com/protected/ * Trying 203.0.113.10:443... * Connected to mtls.example.com (203.0.113.10) port 443 (#0) * TLSv1.3 (IN), TLS handshake, Certificate (11): ##### snipped ##### * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * Server certificate: ##### snipped ##### > GET /protected/ HTTP/1.1 > Host: mtls.example.com ##### snipped ##### < HTTP/1.1 200 OK ##### snipped ##### {"status":"ok","message":"client certificate accepted"}Success signals include an SSL connection using TLSv1.x line for the handshake, a HTTP/1.1 200 OK status, and the expected protected resource body instead of certificate or authentication errors.
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.
Comment anonymously. Login not required.
