<!-- https://pkizerotohero.com/errors/curl-self-signed-certificate-in-certificate-chain/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Fix curl's self-signed certificate in certificate chain error: find the untrusted root (often a proxy or company CA) and trust it properly, without -k.

Source: https://pkizerotohero.com/errors/curl-self-signed-certificate-in-certificate-chain/ · Last reviewed: 2026-10-20

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

[Check yours: Chain checker](https://pkizerotohero.com/tools/chain/)

## 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](https://docs.openssl.org/3.0/man1/openssl-verification-options/)). Older OpenSSL versions spell it “self signed”, without the hyphen.

```text
$ 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.
```

```text
$ 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](https://pkizerotohero.com/handbook/glossary/#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](https://pkizerotohero.com/handbook/glossary/#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](https://pkizerotohero.com/handbook/glossary/#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:

```bash
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:

```bash
# 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:

```bash
openssl s_client -connect git.example.com:443 -servername git.example.com -showcerts </dev/null 2>/dev/null | grep -E '^ [0-9]+ s:|^   i:'
```

```text
 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:

```bash
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](https://pkizerotohero.com/handbook/glossary/#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`.

Warning

Why -k and --insecure are a trap

With `curl -k` or `git -c http.sslVerify=false`, curl accepts any certificate from anyone. On a network with a TLS-inspection proxy, that may seem harmless, but the same flag also accepts an attacker’s certificate on the next network you join. Trust the one root you need instead, with `--cacert` or the system store, and keep checking everything else.

## Check yours

*Interactive tool: Chain checker (runs in the browser at [/tools/chain/](https://pkizerotohero.com/tools/chain/))*

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](https://pkizerotohero.com/handbook/trust-stores/): why sending a root proves nothing.
- [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/): how a client builds the path to its root.
- Related: [unable to get local issuer certificate](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/) and [SELF\_SIGNED\_CERT\_IN\_CHAIN in Node.js](https://pkizerotohero.com/errors/node-self-signed-cert-in-chain/).
- curl’s guide: [SSL Certificate Verification](https://curl.se/docs/sslcerts.html).

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/errors/curl-self-signed-certificate-in-certificate-chain/).

Last reviewed 20 October 2026Edition 0.9

## Learn why it happens

- [Chapter 12: Who do we trust? CAs, trust stores and self-signed certificates](https://pkizerotohero.com/handbook/trust-stores/)
- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)

## Fix it step by step

- [How to check the certificate chain a server sends](https://pkizerotohero.com/guides/check-remote-certificate-chain/)
- [How to work behind a TLS-inspecting corporate proxy, safely](https://pkizerotohero.com/guides/corporate-proxy-certificates/)
- [How to trust a private root CA on macOS](https://pkizerotohero.com/guides/add-root-ca-macos/)
- [How to run a small private CA with step-ca (lab)](https://pkizerotohero.com/guides/private-ca-step-ca/)

## Everything on this topic

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)
- [Private PKI](https://pkizerotohero.com/topics/private-pki/)
