Skip to content
ecdsa.com
curl (OpenSSL backend)curl

Error message

curl: (60) SSL certificate problem: self-signed certificate in certificate chain

Also printed as

  • curl: (60) SSL certificate problem: self signed certificate in certificate chain
  • curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)

OpenSSL 3.0 and later print "self-signed"; OpenSSL 1.1.1 prints "self signed" without the hyphen, so both spellings appear in logs and search results. Newer curl releases (8.22.0 in our test) word it "SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)". Schannel builds of curl on Windows report the case as "SEC_E_UNTRUSTED_ROOT".

curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)

What it means

curl walked the chain the server sent up to a self-signed root certificate, and that root is not in curl's CA bundle. Unlike "unable to get local issuer certificate", the chain is complete: curl can see the root, it just has no reason to trust it. In practice the root almost always belongs to a company CA or to a proxy that intercepts TLS.

Why it happens

How to fix it

  1. 1.

    Find out whose root it is

    Look at the last certificate of the chain. If the subject is a proxy or security vendor, or your company's name, the fix is to trust that root on this machine. If it is an unexpected name on a network you do not control, stop: someone is intercepting the connection.

    bash
    # Print the subject and issuer of every certificate the server (or the proxy) sends
    openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \
      | grep -E '^ *[0-9]+ s:|^ *i:'
    # The last entry has s: == i: — that self-signed root is what curl does not trust.
  2. 2.

    Add the company or proxy root to the system store

    Get the root certificate from your IT department (or export it from a managed machine's trust store) and install it into the operating system bundle that curl uses. Containers need it at build time; WSL needs it inside the Linux distribution, not only in Windows.

    bash
    # Debian, Ubuntu, Alpine
    sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt
    sudo update-ca-certificates
    
    # RHEL, Fedora, Amazon Linux
    sudo cp corp-root-ca.pem /etc/pki/ca-trust/source/anchors/
    sudo update-ca-trust
  3. 3.

    Trust the root for one command or one environment

    --cacert and CURL_CA_BUNDLE replace curl's default bundle, so point them at a file that contains both the public roots and the private one when the same process also talks to public sites.

    bash
    curl --cacert corp-root-ca.pem https://api.example.com/
    
    # Keep public roots too: build one bundle with both
    cat /etc/ssl/certs/ca-certificates.crt corp-root-ca.pem > /opt/certs/bundle.pem
    export CURL_CA_BUNDLE=/opt/certs/bundle.pem
  4. 4.

    Server owners: do not rely on sending the root

    Serve the leaf and the intermediates; distribute the private root to clients through configuration management instead. Including the root wastes bytes and never makes an untrusted root trusted.

    bash
    # Serve leaf + intermediates only; clients must already have the root
    cat api.example.com.crt intermediate.crt > fullchain.pem
  5. 5.

    Temporary diagnostic: see the issuer with verification off

    -k disables certificate checks so the verbose output can show the subject and issuer the server presents. Use it only to identify the root. Left in scripts, -k lets anyone on the path read and change the traffic, including tokens and downloaded installers.

    bash
    # Diagnostic only: shows who issued the certificate even though curl distrusts it
    curl -sv -k -o /dev/null https://api.example.com/ 2>&1 | grep -E 'subject:|issuer:'

Related errors

Browse the full signature error database