Accessing mTLS Websites with Client Certificates on macOS and iOS

This article explains how to configure client certificates on macOS and iOS devices to access HTTPS websites that enable mutual TLS (mTLS). The examples use a private CA, step-ca, Nginx, PKCS#12, and a PIV smart card. All domain names, certificate names, filenames, and IP addresses in this article are examples; replace them with your actual values.

1. mTLS Authentication Process

With ordinary HTTPS, the client primarily validates the server certificate; mTLS additionally requires the server to validate the client certificate.

Basic process:

  1. The client accesses the HTTPS site.
  2. Nginx requests a client certificate during the TLS handshake.
  3. The browser or system selects a client identity and uses the corresponding private key to complete the signing operation.
  4. Nginx validates the client certificate, certificate chain, and validity period according to its configuration.
  5. After validation succeeds, the connection proceeds to the HTTP layer, where the site’s policies determine whether access is permitted.

Client identity and server CA trust are two independent configurations:

  • Client identity: A client certificate and its matching private key, used to prove the client’s identity to the server.
  • Server CA trust: The client trusts the CA that issued the website’s server certificate, allowing it to validate the HTTPS server’s identity.

Installing a client certificate does not automatically cause a device to trust the private CA.

2. macOS: Using a PIV Hardware Key

If the client private key is stored on PIV-compatible hardware, the private key can remain inside the hardware device, avoiding the need to export a private key file. Whether the browser can use the identity depends on support in macOS, the smart card middleware, the key device, and the browser.

General inspection steps:

  1. Insert the PIV hardware key.
  2. Confirm that macOS recognizes the smart card and its certificate.
  3. Open a browser and access the mTLS website.
  4. If the browser requests a client certificate, select the appropriate identity.
  5. Enter the PIN or complete the hardware operation when prompted.
  6. Use the page result and server logs together to confirm whether authentication succeeded.

The following commands can be used to inspect the system state:

security list-smartcards
sc_auth identities
sudo security smartcards token -l

Output may differ between macOS versions, hardware, and middleware. The absence of command output cannot by itself prove that a certificate is invalid.

3. macOS: Importing CRT, PEM, and KEY into the Keychain

If you already have a client certificate and its corresponding private key file, you can package them into a PKCS#12 file (.p12) and import it into the macOS Keychain.

Example files:

  • client.crt: Client certificate.
  • client.key: Private key matching the certificate.
  • intermediate-ca.crt: Intermediate CA certificate, optional.
  • root-ca.crt: Root CA certificate, used to validate the certificate chain or configure server trust.

Work in a controlled directory and avoid committing private keys or PKCS#12 files to a source code repository.

3.1 Inspecting the Client Certificate

openssl x509 -in client.crt -noout \
  -subject -issuer -serial -dates

Inspect the certificate usage:

openssl x509 -in client.crt -noout -ext extendedKeyUsage

The certificate should include TLS Web Client Authentication, with the usage OID 1.3.6.1.5.5.7.3.2.

Verify that the certificate and private key match:

openssl x509 -in client.crt -pubkey -noout |
  openssl pkey -pubin -outform DER |
  shasum -a 256

openssl pkey -in client.key -pubout -outform DER |
  shasum -a 256

The digests output by the two commands should match.

If an intermediate CA is available, you can validate the certificate chain:

openssl verify \
  -CAfile root-ca.crt \
  -untrusted intermediate-ca.crt \
  client.crt

If there is no intermediate CA:

openssl verify -CAfile root-ca.crt client.crt

If the chain contains multiple levels of intermediate CAs, provide the complete and correct intermediate certificate chain.

3.2 Generating a PKCS#12 File Compatible with the macOS Keychain

Some PKCS#12 files may still fail to import into the Keychain despite having the correct password, due to compatibility issues with their encryption parameters. You can explicitly specify legacy PKCS#12 encryption parameters to export for compatibility.

If an intermediate CA certificate is available:

umask 077

openssl pkcs12 -export \
  -inkey client.key \
  -in client.crt \
  -certfile intermediate-ca.crt \
  -name "Client Certificate" \
  -keypbe PBE-SHA1-3DES \
  -certpbe PBE-SHA1-3DES \
  -macalg sha1 \
  -out client-macos.p12

If there is no intermediate CA, remove the -certfile intermediate-ca.crt line.

Set a non-empty export password when running the command. It is recommended to begin with Latin letters and digits to reduce troubleshooting complications caused by differences in password encoding.

Validate the file:

openssl pkcs12 -info -in client-macos.p12 -noout

Enter the password you just set. If OpenSSL can read the file but the Keychain still refuses to import it, first inspect the compatibility of the PKCS#12 encryption parameters.

SHA-1 and 3DES are used here only for PKCS#12 file encapsulation compatibility; they do not mean that legacy algorithms should be enabled in TLS connections. They do not change the client certificate’s public key, private key, or extended key usage.

3.3 Importing into the Keychain

Open Keychain Access:

open -a "Keychain Access"

Then:

  1. Select the login keychain.
  2. Select “File → Import Items.”
  3. Select client-macos.p12.
  4. Enter the export password.
  5. Find the corresponding identity under “My Certificates.”
  6. Expand the certificate and confirm that the corresponding private key exists beneath it.

You can also import it from the command line:

security import ./client-macos.p12 \
  -k "$HOME/Library/Keychains/login.keychain-db" \
  -f pkcs12

Inspect identities available for SSL client authentication:

security find-identity -v -p ssl-client \
  "$HOME/Library/Keychains/login.keychain-db"

If no valid identity is found, check whether the certificate and private key match, whether the certificate is valid, and whether it has a client authentication usage.

4. Configuring Server CA Trust

If the website uses a private CA to issue its server certificate, the client must also trust the correct root CA.

First, inspect the root certificate details and fingerprint:

openssl x509 -in root-ca.crt -noout \
  -subject -issuer -fingerprint -sha256

After confirming its source and fingerprint, import the root CA into the certificate trust store on the relevant device. If system-wide trust is required, install it in the macOS System keychain and configure trust settings according to its actual use; this usually requires administrator privileges.

Do not treat a client certificate as a root CA, and do not install CA private keys on client devices. The server should correctly send any required intermediate certificates.

5. Verifying mTLS with curl

Before testing in a browser, you can use curl to validate the certificates, private key, and server configuration.

curl -v \
  --cert client.crt \
  --key client.key \
  --cacert root-ca.crt \
  https://mtls.example.test/

If you need to specify the server IP while retaining the correct hostname and SNI:

curl -v \
  --resolve mtls.example.test:443:192.0.2.10 \
  --cert client.crt \
  --key client.key \
  --cacert root-ca.crt \
  https://mtls.example.test/

The example IP 192.0.2.10 is a documentation-specific address; replace it with the actual server IP.

If the server requires the client to send an intermediate CA certificate, you can combine the client certificate with the intermediate certificate:

cat client.crt intermediate-ca.crt > client-chain.pem

Then replace --cert client.crt with --cert client-chain.pem.

Common outcomes:

  • HTTP 200: The request succeeded, generally indicating that both TLS and the application request passed.
  • HTTP 401/403: TLS may have completed, but the request was rejected by an application-layer authorization policy.
  • certificate required: The server required a client certificate but did not receive a usable certificate.
  • unknown ca: The server may not trust the CA that issued the client certificate.
  • certificate verify failed: Inspect the server certificate chain, root CA, and domain name.

Use the client output and Nginx logs together. The HTTP status code alone cannot fully verify that every stage of mTLS worked correctly.

6. Testing on macOS with Chrome and Safari

Chrome

  1. Confirm that the client identity is installed and that the Keychain contains the matching private key.
  2. Completely quit and reopen Chrome.
  3. Access the mTLS website.
  4. If the browser requests a client certificate, select the correct identity.
  5. Inspect the page result and server logs.

If the certificate does not appear, check its usage, private key availability, and whether the server requested a client certificate during the current TLS handshake.

Safari

Test separately using the same URL. Safari and Chrome may differ in their certificate selection behavior, so do not assume that success in one browser means the other will use the same identity.

If an error similar to “the required client certificate was not sent” appears, check whether the browser selected and sent the correct identity, whether the system recognizes that identity, and whether the server received the client certificate during that handshake.

7. iOS: Installing a Client Identity

An iPhone can use a standalone client certificate. A common approach is to generate a dedicated private key and CSR in a controlled environment, have an existing CA issue the client certificate, and then export a PKCS#12 file for installation on iOS.

7.1 Generating the Private Key and CSR

mkdir -p ~/ios-mtls
cd ~/ios-mtls
umask 077

Generate a P-256 private key:

openssl ecparam \
  -genkey \
  -name prime256v1 \
  -noout \
  -out ios-client.key

Generate the CSR:

openssl req \
  -new \
  -key ios-client.key \
  -subj "/CN=Mobile Client" \
  -out ios-client.csr

Set an appropriate identity name according to your actual device and certificate management requirements.

7.2 Issuing with step-ca

step ca sign \
  ios-client.csr \
  ios-client.crt \
  --not-after=8760h

The actual validity period is governed by CA policy. After issuance, inspect the certificate:

openssl x509 \
  -in ios-client.crt \
  -noout \
  -subject \
  -issuer \
  -dates \
  -ext extendedKeyUsage

Confirm that the issuer, validity period, and client authentication usage meet your expectations. If clientAuth is absent, first correct the CA’s issuance policy.

7.3 Exporting PKCS#12

If an intermediate CA certificate is available:

openssl pkcs12 -export \
  -inkey ios-client.key \
  -in ios-client.crt \
  -certfile intermediate-ca.crt \
  -name "Mobile Client" \
  -keypbe PBE-SHA1-3DES \
  -certpbe PBE-SHA1-3DES \
  -macalg sha1 \
  -out ios-client.p12

If there is no intermediate CA, remove the -certfile intermediate-ca.crt argument.

Set a non-empty password for the file and validate it locally:

openssl pkcs12 -info -in ios-client.p12 -noout

Do not package the root CA or intermediate CA private keys into the client file. The PKCS#12 file should contain only the private key, client certificate, and necessary certificate chain required for that client identity.

7.4 Installing on iPhone

  1. Transfer the .p12 file to the iPhone in a controlled manner.
  2. Open the file and follow the system prompts to install the certificate identity or configuration profile.
  3. Enter the PKCS#12 export password.
  4. Confirm the installation on the relevant Profiles or Device Management page in Settings.
  5. Confirm that the installed identity contains the client certificate and private key, rather than only the public certificate.

The interface wording may differ slightly between iOS versions. If the website uses a server certificate issued by a private CA, you must also install and trust the root CA separately according to the system’s requirements.

7.5 Accessing It with Safari

  1. Confirm that the iPhone is connected to the home network or can access the internal network through a VPN.
  2. Open the mTLS website in Safari.
  3. When the system or browser requests a client identity, select the corresponding certificate.
  4. Inspect the page result and server logs.

If access fails, investigate DNS, network reachability, server certificate trust, client identity installation, and server-side validation logs separately.

8. iOS Browser and Application Compatibility

Whether browsers and other applications on iOS can use a system-installed client certificate depends on the application’s implementation, the operating system interfaces, and the specific version. The mere fact that a certificate has been installed does not mean that every application can use it.

Test each target application individually:

  • Safari: First verify that the system client identity is usable.
  • Chrome: Separately test the current version’s support for client certificates.
  • Other applications: Confirm that the application supports mTLS, can access system identities, and supports the target server address.

If an application cannot invoke a system identity, reissuing the certificate or changing the key algorithm usually cannot resolve an application-layer access restriction by itself.

9. Certificate and Private Key Security

Client certificates can generally be public, but their corresponding private keys must be protected.

  • Treat both .key files and .p12 files as sensitive material.
  • Do not submit private keys or PKCS#12 files to public repositories, blog attachments, or untrusted file-sharing services.
  • After a transfer is complete, inspect temporary directories, download directories, and file transfer records for any unnecessary remaining copies.
  • If backups are needed, store them securely using encryption.
  • If a private key is exposed, revoke the corresponding client certificate or stop trusting it, and regenerate the key and certificate.
  • When storing a private key on a hardware key, avoid exporting it whenever possible.

Importing a private key from a Mac onto a phone is not equivalent to generating a hardware-bound, non-exportable private key within the phone’s secure hardware.

10. Troubleshooting Order

Step 1: Check the Network and DNS

Confirm that the client can resolve the target domain name and connect to Nginx’s HTTPS port.

Step 2: Check Server Certificate Trust

Confirm that the client trusts the CA that issued the website’s server certificate and that the server certificate’s domain name, validity period, and certificate chain are correct.

Step 3: Check the Client Identity

Confirm that the client certificate is valid, includes the clientAuth usage, and that the corresponding private key is available. For the macOS Keychain approach, run:

security find-identity -v -p ssl-client \
  "$HOME/Library/Keychains/login.keychain-db"

Step 4: Check Nginx

Check that the CA file referenced by ssl_client_certificate contains the correct client certificate issuance chain, and confirm that ssl_verify_client and ssl_verify_depth match the actual CA hierarchy.

Step 5: Distinguish Between No Certificate Sent and Validation Failure

  • No required SSL certificate was sent: Usually means that the server-required client certificate was not received during that TLS handshake.
  • Client certificate validation failed: May be related to the certificate chain, validity period, usage, or server-side trust configuration.

Use the Nginx error log and the client’s actual behavior together. Do not draw conclusions solely from the browser’s error page.

11. Summary

PlatformClient Identity ApproachConsiderations
macOS ChromePIV hardware key or Keychain identitySeparately validate certificate selection and hardware support
macOS SafariPIV hardware key or Keychain identityDo not assume behavior is exactly the same as Chrome
iOS SafariSystem-installed PKCS#12 identitySeparately confirm server CA trust
iOS ChromeDepends on application and system version supportMust be tested on the target version

The recommended approach is to prefer keeping the private key on a PIV hardware key on the Mac. When a software identity is needed, use a compatible PKCS#12 file imported into the Keychain. On iPhone, use a separate client identity and validate it individually through each target application.

If mTLS must later be enabled for native applications such as password managers, design the application’s own network request authentication separately from browser-based mTLS access to websites.