- curl
- Exact text checked with the real tool
SSL certificate problem: self-signed certificate in certificate chain
Also seen in: git, PHP, other programs built on libcurl
In one sentence
The server sent a chain that ends in a root certificate curl does not trust, most often the root of a company CA or of a TLS-inspection proxy.
Where you see it
curl and git print this with curl’s exit code 60. The text comes from OpenSSL’s verify error 19 (X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, see the OpenSSL verify documentation). Older OpenSSL versions spell it “self signed”, without the hyphen.
$ curl https://registry.example.com/
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
$ git clone https://git.example.com/team/app.git
Cloning into 'app'...
fatal: unable to access 'https://git.example.com/team/app.git/': SSL certificate problem: self-signed certificate in certificate chain
What it means
A root certificate signs itself: its issuer and subject are the same. That is normal. A root is trusted only because it is already in your trust store, never because the server sends it. Here the server sent a complete chain up to a self-signed root, and curl looked for that root in its CA bundle and did not find it. So the chain is complete, but it ends at a root curl has no reason to believe.
Likely causes
A TLS-inspection proxy re-signs your traffic
If this happens for public sites such as github.com or pypi.org on a company network, a TLS-inspection proxy or an antivirus product is probably decrypting HTTPS and re-signing it with its own root. Browsers accept it because IT installed that root in the operating system’s store; curl uses its own bundle.
Check who issued the certificate you receive for a public site:
openssl s_client -connect github.com:443 -servername github.com </dev/null 2>/dev/null | openssl x509 -noout -issuer
If the issuer names your company or a security product, that is the cause.
Fix: ask IT for the proxy’s root certificate (PEM format) and add it to the system store:
# Debian and Ubuntu (the file must end in .crt)
sudo cp proxy-root.pem /usr/local/share/ca-certificates/proxy-root.crt
sudo update-ca-certificates
# RHEL, Fedora and similar
sudo cp proxy-root.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
On Windows, IT has usually installed the root already. Make git use the Windows store with git config --global http.sslBackend schannel; the curl.exe that ships with Windows uses that store anyway.
The server uses a private CA you have not trusted yet
Internal servers often send their company root along with the chain. List what the server sends:
openssl s_client -connect git.example.com:443 -servername git.example.com -showcerts </dev/null 2>/dev/null | grep -E '^ [0-9]+ s:|^ i:'
0 s:CN=git.example.com
i:O=Example Corp, CN=Example Corp Issuing CA
1 s:O=Example Corp, CN=Example Corp Issuing CA
i:O=Example Corp, CN=Example Corp Root CA
2 s:O=Example Corp, CN=Example Corp Root CA
i:O=Example Corp, CN=Example Corp Root CA
The last entry has the same subject (s:) and issuer (i:): that is the self-signed root. Get the root from your PKI team through a trusted channel (not by copying it from this output), check its fingerprint with them, and test it:
openssl x509 -in corp-root.pem -noout -fingerprint -sha256
curl --cacert corp-root.pem https://git.example.com/
Then add it to the system store as shown above, or for git only: git config --global http.https://git.example.com/.sslCAInfo /path/to/corp-root.pem.
An old CA bundle lacks a newer public root
If the root is a well-known public CA, your system’s bundle may be too old to contain it. Update the operating system or the ca-certificates package.
Related: the server certificate itself is self-signed
If the message is SSL certificate problem: self-signed certificate (no “in certificate chain”), the server sent a single self-signed certificate with no CA at all. For anything other than a quick local test, replace it with a certificate from your company CA or a public CA such as Let’s Encrypt. For a local test, pass that certificate with --cacert.
Check yours
Paste every certificate from openssl s_client -showcerts. The checker marks the self-signed root at the top, explains when a client would trust it, and points out root names that belong to known HTTPS-inspection products.
Learn why
- Who do we trust? CAs, trust stores and self-signed certificates: why sending a root proves nothing.
- Certificate chains and path validation: how a client builds the path to its root.
- Related: unable to get local issuer certificate and SELF_SIGNED_CERT_IN_CHAIN in Node.js.
- curl’s guide: SSL Certificate Verification.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.