• 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.

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

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

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

In the gameLevel 12: Who do we trust?Registry of Roots, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9