fjordtools / Errors / self-signed in certificate chain

self-signed certificate in certificate chain

Every chain ends in a root certificate that signs itself. That is normal. The error means the client reached such a root and does not have it in its trusted store. The usual causes are a private or test CA, and a proxy that re-signs traffic.

Fjordtools team · Seen in OpenSSL, curl, Node and Python · Published · 2 min read

How the tools word it

OpenSSL 3.6 s_clientverify error:num=19:self-signed certificate in certificate chain
OpenSSL 3.6 verifyerror 19 at 2 depth lookup: self-signed certificate in certificate chain
curl 8.23curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)
Node 26 tlsSELF_SIGNED_CERT_IN_CHAIN: self-signed certificate in certificate chain
Python 3.14 sslssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1082)

Common causes

  1. The root is a private or test CA

    The chain below is complete and in order, and it ends in a root that is not in a public trust store. The error is raised for the third certificate, depth 2 in OpenSSL. A client trusts it only after you add that root.

    Input

    -----BEGIN CERTIFICATE-----
    MIIB7jCCAZWgAwIBAgIBBDAKBggqhkjOPQQDAjBBMRgwFgYDVQQKDA9mam9yZHRv
    b2xzIHRlc3QxJTAjBgNVBAMMHGZqb3JkdG9vbHMgdGVzdCBpbnRlcm1lZGlhdGUw
    HhcNMjYwMTAxMDAwMDAwWhcNMzYwMTAxMDAwMDAwWjAwMRgwFgYDVQQKDA9mam9y
    ZHRvb2xzIHRlc3QxFDASBgNVBAMMC3NlcnZlci50ZXN0MFkwEwYHKoZIzj0CAQYI
    KoZIzj0DAQcDQgAE7ee19O78yqmyOB5x/h4K7/QA6YW32XJesJT8lv2Q+Y0GBR4e
    r2DpKzlXcNQ5G7VN9GkztuzSnXch/wKr38FQdqOBjjCBizAMBgNVHRMBAf8EAjAA
    MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAWBgNVHREEDzAN
    ggtzZXJ2ZXIudGVzdDAdBgNVHQ4EFgQU/HjqRgLgUEQQDnr1y8nhCcb2baowHwYD
    VR0jBBgwFoAUMt7RDnv6EJHcKso7bd3VWZC9krowCgYIKoZIzj0EAwIDRwAwRAIg
    VatubrykL+ZDu2wIXIFzNCA4G6SIsUQnf2mw4c30KXMCICf1jBo0UciVihHiS1KE
    k6cc1SY9pCBuOPjwOoiC3ob3
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIBzzCCAXWgAwIBAgIBAzAKBggqhkjOPQQDAjA5MRgwFgYDVQQKDA9mam9yZHRv
    b2xzIHRlc3QxHTAbBgNVBAMMFGZqb3JkdG9vbHMgdGVzdCByb290MB4XDTI2MDEw
    MTAwMDAwMFoXDTM2MDEwMTAwMDAwMFowQTEYMBYGA1UECgwPZmpvcmR0b29scyB0
    ZXN0MSUwIwYDVQQDDBxmam9yZHRvb2xzIHRlc3QgaW50ZXJtZWRpYXRlMFkwEwYH
    KoZIzj0CAQYIKoZIzj0DAQcDQgAE8S+llYHyI61wNUPj0DPhpIyF+hRxM7dJFQdo
    lkEFplO8LVCXy7RgHxRttTDP+Od2kJV2tmdXrQXeLPMGOrG0C6NmMGQwEgYDVR0T
    AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDLe0Q57+hCR
    3CrKO23d1VmQvZK6MB8GA1UdIwQYMBaAFLcg9Ms28bssX2hEC2iuZ/lj9P4oMAoG
    CCqGSM49BAMCA0gAMEUCIQDEsN76EmigQQoI21DUJpyRdSt2nQdhomZ1IAkxNuLe
    4wIgL4BbGPQ84ODnLT1cTE2A2olMZgsjQDfiaRDDuW41iS8=
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIBwzCCAWqgAwIBAgIBATAKBggqhkjOPQQDAjA5MRgwFgYDVQQKDA9mam9yZHRv
    b2xzIHRlc3QxHTAbBgNVBAMMFGZqb3JkdG9vbHMgdGVzdCByb290MB4XDTI2MDEw
    MTAwMDAwMFoXDTM2MDEwMTAwMDAwMFowOTEYMBYGA1UECgwPZmpvcmR0b29scyB0
    ZXN0MR0wGwYDVQQDDBRmam9yZHRvb2xzIHRlc3Qgcm9vdDBZMBMGByqGSM49AgEG
    CCqGSM49AwEHA0IABH0DdyM4j2Qm5yUYrP9opJIyAW5oKmYs1KlgD5QaEspetzDt
    LmYCaEdSk051mmCgtov0ROqWiElrHplTLeJ0vQWjYzBhMB8GA1UdIwQYMBaAFLcg
    9Ms28bssX2hEC2iuZ/lj9P4oMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
    AgEGMB0GA1UdDgQWBBS3IPTLNvG7LF9oRAtormf5Y/T+KDAKBggqhkjOPQQDAgNH
    ADBEAiBJvWgbvsYiq9JtmZfKyepnmBnyzFQ93G+Tu9MHiZSt9wIgLz6fgDWJkUSD
    djhBBe3H4vsZwwTf+tNiFatZWY+Bkno=
    -----END CERTIFICATE-----

    Certificate Decoder says

    leaf · issued by 2
    intermediate · issued by 3
    root · self-signed
  2. The server's own certificate is self-signed

    When the leaf itself signs itself, the message loses the words in certificate chain: self-signed certificate, error 18 at depth 0 in OpenSSL, DEPTH_ZERO_SELF_SIGNED_CERT in Node. The cause is the same, a certificate no trusted CA vouches for.

    Input

    -----BEGIN CERTIFICATE-----
    MIIBhDCCASmgAwIBAgIBBjAKBggqhkjOPQQDAjAWMRQwEgYDVQQDDAtzZXJ2ZXIu
    dGVzdDAeFw0yNjAxMDEwMDAwMDBaFw0zNjAxMDEwMDAwMDBaMBYxFDASBgNVBAMM
    C3NlcnZlci50ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5AQZINGTVGz2
    E4ip0sTj/Q9uZjvCjo8aBDmw2fAZGPEnFKRN8E1iv4qf5LuNdsavJmMZ62Ky4XY9
    xCKZleexTaNoMGYwHQYDVR0OBBYEFAcRP2dk59U5vcQBSxUqur3W1NImMB8GA1Ud
    IwQYMBaAFAcRP2dk59U5vcQBSxUqur3W1NImMAwGA1UdEwEB/wQCMAAwFgYDVR0R
    BA8wDYILc2VydmVyLnRlc3QwCgYIKoZIzj0EAwIDSQAwRgIhAKsK71tbzp8UA4fs
    VaX3xgi2yYNRRo211NJ6TpSuV2qMAiEA16zHBGn60FM11g6YiRwqXdsVL5oW6Tf+
    3Z5agmDeGNM=
    -----END CERTIFICATE-----

    Certificate Decoder says

    Issuer · Same as the subject · Self-signed
  3. A proxy re-signs the traffic

    A company proxy or antivirus that opens TLS traffic ends every chain in its own root. If a public website gives this error only on one network, suspect that, and compare with another network.

How to fix it

  1. Give the client the CA's root certificate: curl --cacert ca.pem, NODE_EXTRA_CA_CERTS=ca.pem for Node, SSL_CERT_FILE=ca.pem for Python and OpenSSL, or add it to the system's trust store.
  2. Paste the chain into the Certificate Decoder to see which certificate is the self-signed one and who issued the rest.
  3. For development, make one private CA and issue every test certificate from it, so you add one root to your machines. mkcert does this.
  4. If a public site gives the error only on your network, ask the network's owner for the proxy's CA certificate instead of working around it.
  5. Do not turn verification off (curl -k, NODE_TLS_REJECT_UNAUTHORIZED=0, verify=False). That removes the protection for every server, not just this one.

Related errors

More error explainers are coming

See all errors.