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
A TLS-inspecting proxy or antivirus re-signs connections
commonZscaler, Netskope, Fortinet, Palo Alto and many antivirus products terminate TLS and present certificates issued by their own root, which they send in the chain. The browser trusts that root through the operating system, but curl inside WSL, Docker, Git Bash or a CI runner reads its own bundle and rejects it. The certificate's issuer names the proxy vendor or your company, not the site's real CA.
The service uses a private root and sends it in the chain
commonInternal services, Kubernetes ingresses and appliances often use a certificate from a company or self-made root and include that root in the served chain. Sending the root does not make it trusted: a trust anchor must already be on the client.
The private root is installed in a different store
occasionalThe root was added to the Windows certificate store, the macOS keychain or Java's cacerts, but this curl reads an OpenSSL bundle such as /etc/ssl/certs/ca-certificates.crt or the copy shipped with Git for Windows, Homebrew or conda.
The client's public root store is out of date
rareRarely, a server sends a newer public root that an old, never-updated client does not have. Updating the ca-certificates package fixes it; if the root is genuinely public, other machines will verify the site fine.
How to fix it
- 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.
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.
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.
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.
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
curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)self-signed certificate in certificate chainNode.js tls / https / fetch[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)