fjordtools / Errors / unable to verify the first cert

unable to verify the first certificate

Node builds a chain from the certificates the server sends up to a root it trusts. This message means it could not take even the first step: the server sent only its own certificate, and Node does not fetch the missing intermediate. A browser often does, so the site can open in a browser and still fail in Node.

Fjordtools team · Seen in Node, npm and fetch · Published · 2 min read

How Node and the others word it

Node 26 tlsUNABLE_TO_VERIFY_LEAF_SIGNATURE: unable to verify the first certificate; if the root CA is installed locally, try running Node.js with --use-system-ca
Node 26 fetchTypeError: fetch failed, with cause UNABLE_TO_VERIFY_LEAF_SIGNATURE: unable to verify the first certificate; if the root CA is installed locally, try running Node.js with --use-system-ca
Node 26, intermediate sent, root unknownUNABLE_TO_GET_ISSUER_CERT_LOCALLY: unable to get local issuer certificate
OpenSSL 3.6 s_client, same serververify error:num=20:unable to get local issuer certificate
curl 8.23, same servercurl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
Python 3.14 ssl, same serverssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1082)

Common causes

  1. The server sends only its own certificate

    Node looks for the issuer of the server's certificate among the certificates the server sent with it. There were none, so it stops at the first certificate and says so. The Certificate Decoder shows the same thing: one certificate, and its issuer is not in the input.

    Input

    -----BEGIN CERTIFICATE-----
    MIIB7jCCAZWgAwIBAgIBBDAKBggqhkjOPQQDAjBBMRgwFgYDVQQKDA9mam9yZHRv
    b2xzIHRlc3QxJTAjBgNVBAMMHGZqb3JkdG9vbHMgdGVzdCBpbnRlcm1lZGlhdGUw
    HhcNMjYwMTAxMDAwMDAwWhcNMzYwMTAxMDAwMDAwWjAwMRgwFgYDVQQKDA9mam9y
    ZHRvb2xzIHRlc3QxFDASBgNVBAMMC3NlcnZlci50ZXN0MFkwEwYHKoZIzj0CAQYI
    KoZIzj0DAQcDQgAE7ee19O78yqmyOB5x/h4K7/QA6YW32XJesJT8lv2Q+Y0GBR4e
    r2DpKzlXcNQ5G7VN9GkztuzSnXch/wKr38FQdqOBjjCBizAMBgNVHRMBAf8EAjAA
    MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAWBgNVHREEDzAN
    ggtzZXJ2ZXIudGVzdDAdBgNVHQ4EFgQU/HjqRgLgUEQQDnr1y8nhCcb2baowHwYD
    VR0jBBgwFoAUMt7RDnv6EJHcKso7bd3VWZC9krowCgYIKoZIzj0EAwIDRwAwRAIg
    VatubrykL+ZDu2wIXIFzNCA4G6SIsUQnf2mw4c30KXMCICf1jBo0UciVihHiS1KE
    k6cc1SY9pCBuOPjwOoiC3ob3
    -----END CERTIFICATE-----

    Certificate Decoder says

    leaf · issuer not in the input
  2. The intermediate is sent but the root is not trusted

    Here the server did its part, the certificate and its intermediate, and Node words the failure differently: unable to get local issuer certificate. The missing piece is then the root, which must come from the client's own trust store. A private CA, or a machine whose operating system trusts a root that Node's own list does not, causes it.

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

    Certificate Decoder says

    leaf · issued by 2
    intermediate · issuer not in the input
  3. Another client accepts the same server

    Whether a client can complete a chain on its own depends on the client. Node, OpenSSL, curl and Python did not download a missing intermediate in our tests, even from a certificate that named the address where its issuer can be downloaded (CA issuers in the Decoder). A server that leaves the intermediate out fails in them, whatever another client does.

How to fix it

  1. Make the server send the full chain: its certificate first, then each intermediate, with the root left out. Let's Encrypt clients write that as fullchain.pem; nginx takes it in ssl_certificate.
  2. See what the server sends with openssl s_client -connect example.com:443 -servername example.com -showcerts, and paste it into the Certificate Decoder: the chain table shows each link and where one is missing.
  3. If the root is missing on the client, give Node the CA certificate with NODE_EXTRA_CA_CERTS=ca.pem. Node also takes --use-system-ca, the option its own message names, which makes it read the operating system's trusted roots; on Linux it follows SSL_CERT_FILE.
  4. Do not set NODE_TLS_REJECT_UNAUTHORIZED=0 to make the message go away. It turns off certificate checking for the whole process, and Node prints a warning saying so.

Related errors

More error explainers are coming

See all errors.