HTTPS APIs and internal services often rely on custom CRT files for certificate-based security. When default trust stores do not include the required issuer or when mutual TLS is enforced, requests must present specific CA certificates and client certificates so that connections are accepted. Correct configuration with cURL avoids confusing TLS errors and maintains strong authentication.

During a TLS handshake, the server presents its certificate, which is validated against a trusted CA certificate. cURL normally relies on the system trust store, but the --cacert option allows explicit selection of a CA CRT for endpoints signed by private or internal authorities. For mutual TLS, cURL also sends a client certificate from --cert together with a matching private key from --key so the server can verify client identity.

Reliable operation depends on a build of cURL with HTTPS support, access to the required CRT and key files, and secure handling of private keys. Incorrect certificate chains or mismatched keys cause handshake failures, while disabling verification with options such as --insecure undermines confidentiality and integrity. Private key files should remain tightly permissioned to prevent disclosure to other local users or processes.

Steps to use client certificates and CA certificates with cURL:

  1. Confirm that cURL is installed and shows a TLS backend in the version output.
    $ curl --version
    curl 8.5.0 (aarch64-unknown-linux-gnu) libcurl/8.5.0 OpenSSL/3.0.13 zlib/1.3 brotli/1.1.0 zstd/1.5.5 libidn2/2.3.7 libpsl/0.21.2 (+libidn2/2.3.7) libssh/0.10.6/openssl/zlib nghttp2/1.59.0 librtmp/2.3 OpenLDAP/2.6.7
    Release-Date: 2023-12-06, security patched: 8.5.0-2ubuntu10.6
    Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
    Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd

    Presence of OpenSSL, GnuTLS, or another TLS backend in the version output confirms HTTPS support.

  2. Identify whether the target endpoint requires only a custom CA certificate, a client CRT, or full mutual TLS with both certificate and private key.

    API or service documentation usually specifies the required certificate chain, file formats, and authentication flow.

  3. Provide the appropriate CA certificate to cURL with --cacert when the default trust store does not contain the issuer.
    $ curl --cacert /work/docker/certs/ca.crt --verbose --silent https://api.example.net/ --output /dev/null
    * Host api.example.net:443 was resolved.
    * IPv6: (none)
    * IPv4: 127.0.0.1
    ##### snipped #####
    *  CAfile: /work/docker/certs/ca.crt
    ##### snipped #####
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
    * Server certificate:
    *  subject: C=US; ST=State; L=City; O=Example Org; OU=Ops; CN=api.example.net
    *  start date: Jan 10 03:56:40 2026 GMT
    *  expire date: Apr 14 03:56:40 2028 GMT
    *  issuer: C=US; ST=State; L=City; O=Example Org; OU=Ops; CN=Example Internal CA
    > GET / HTTP/1.1
    > Host: api.example.net
    ##### snipped #####

    Option --cacert overrides the default trust store and restricts server validation to certificates signed by the provided CA file.

  4. Pass a client certificate with --cert when the server requires mutual TLS authentication.
    $ curl --silent --cert /work/docker/certs/client.crt --key /work/docker/certs/client.key --cacert /work/docker/certs/ca.crt https://api.example.net:4443/mtls
    {
      "message": "client certificate accepted",
      "status": "ok"
    }

    Option --cert loads the X.509 client certificate used to represent the calling client during the TLS handshake.

  5. Attach the matching private key with --key if the certificate and key are stored in separate files.
    $ curl --silent --cert /work/docker/certs/client.crt --key /work/docker/certs/client.key --cacert /work/docker/certs/ca.crt https://api.example.net:4443/mtls
    {
      "message": "client certificate accepted",
      "status": "ok"
    }

    The private key specified by --key must correspond exactly to the public key embedded in the client certificate, or the TLS handshake fails.

  6. Restrict filesystem permissions on the private key so that only the intended user account can read it.
    $ chmod 600 /work/docker/certs/client.key

    Loose permissions on private key files allow other local users or processes to impersonate the client certificate holder.

  7. Avoid disabling certificate verification with --insecure except for short-lived diagnostics in controlled environments.
    $ curl --insecure https://mismatch.example.net/headers --resolve mismatch.example.net:443:127.0.0.1
    {
      "headers": {
        "Accept": "*/*",
        "Host": "mismatch.example.net",
        "User-Agent": "curl/8.5.0"
      }
    }

    Using --insecure skips TLS certificate verification and exposes traffic to man-in-the-middle attacks and spoofed endpoints.

  8. Verify successful configuration by running a verbose request that returns the expected HTTP status code without certificate errors.
    $ curl --cert /work/docker/certs/client.crt --key /work/docker/certs/client.key --cacert /work/docker/certs/ca.crt --verbose --silent https://api.example.net:4443/mtls --output /dev/null
    ##### snipped #####
    * TLSv1.3 (IN), TLS handshake, Request CERT (13):
    ##### snipped #####
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
    > GET /mtls HTTP/1.1
    > Host: api.example.net:4443
    ##### snipped #####
    < HTTP/1.1 200 OK
    < Server: sg-httpbin-lite/1.0 Python/3.12.3
    < Date: Sat, 10 Jan 2026 06:21:42 GMT
    < Content-Length: 64
    < Content-Type: application/json
    ##### snipped #####

    Success signals include absence of certificate warnings, a completed SSL connection line, and the expected HTTP status and response body.