Skip to content
RU

Unable to Verify the First Certificate (UNABLE_TO_VERIFY_LEAF_SIGNATURE): Fix

What it means: The server sent only its own certificate, so the client could not check who signed it.

  1. Count the certificates with -showcerts
  2. Find the missing intermediate via AIA
  3. Serve leaf plus intermediate (fullchain)
  4. Check custom ca options in your code

Test your URL now

Runs a full TLS check: handshake, protocol versions, certificate chain, trust and expiry.

Check your site's SSL →

What unable to verify the first certificate means

The message is OpenSSL verification error 21, X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE. OpenSSL's manual: «No signatures could be verified because the chain contains only one certificate and it is not self-signed and the X509_V_FLAG_PARTIAL_CHAIN flag is not set» (X509_STORE_CTX_get_error). «The first certificate» is the server's own (leaf) certificate, depth 0 in the chain.

Node.js is where most people meet it: Error: unable to verify the first certificate with code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', raised by https, fetch, axios, npm and anything else on Node's TLS stack (Node.js X509 error codes). Recent Node versions append a hint to try --use-system-ca when the root is installed locally; for this particular error the hint rarely helps, because what is missing is an intermediate the server should have sent, not a root.

It is a server configuration problem in the vast majority of cases. The client did its job: it received one certificate, signed by an issuer it does not have, and refused to guess.

Why it happens

SituationWhat you seeFix
Server configured with cert.pem instead of fullchain.pemBrowsers work, Node.js and curl failPoint the server at the full chain
Certificate renewed, chain file not updatedStarted on the renewal dateRebuild the chain from the new intermediate
Load balancer or CDN has the leaf uploaded without the chainOrigin is fine, the public hostname failsUpload the intermediate with the certificate
Client code passes its own ca optionOnly your service fails; curl to the same URL worksInclude the full trust you need, or remove ca

The reason browsers keep working is Authority Information Access (AIA) fetching: the leaf names a URL where its issuer can be downloaded, and Chrome uses it. Node.js, curl, Python and Go verify with what the server sends plus their trust store and do not download anything. A chain that is only browser-complete is broken for every API client.

The last row is a Node.js trap. The Node.js documentation notes that neither the built-in roots nor NODE_EXTRA_CA_CERTS are used when the ca option is set explicitly on a TLS or HTTPS client. Passing ca: [intermediate] therefore replaces Node's whole root list instead of adding to it.

Find the missing intermediate

See what the server sends:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
1

One certificate is the problem. Save the leaf and read where its issuer lives:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 > leaf.pem
openssl x509 -in leaf.pem -noout -issuer -ext authorityInfoAccess

The CA Issuers - URI: line is the download address of the intermediate. Fetch it, convert it to PEM if it is DER, and check that it really signed the leaf before you deploy anything:

curl -sS -o issuer.der 'http://ca.example.net/issuer.crt'
openssl x509 -inform DER -in issuer.der -out issuer.pem
openssl verify -partial_chain -CAfile issuer.pem leaf.pem
leaf.pem: OK
cat leaf.pem issuer.pem > fullchain.pem

Prefer the chain file your CA or ACME client already gives you (Certbot writes fullchain.pem); rebuild it by hand only when that file is lost.

How to fix it

  1. nginx: ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; with the leaf first and intermediates after it, then nginx -t && systemctl reload nginx.
  2. Apache 2.4.8 and later: put the leaf and intermediates in the file named by SSLCertificateFile.
  3. Load balancers and CDNs: upload the certificate together with its chain field; a bare leaf reproduces this error for every non-browser client.
  4. Your own Node.js client talking to a private CA: trust the root, not the intermediate, and keep the defaults by using the environment variable rather than ca:
    NODE_EXTRA_CA_CERTS=/etc/ssl/private-root.pem node app.js
  5. Do not ship a workaround such as rejectUnauthorized: false or NODE_TLS_REJECT_UNAUTHORIZED=0; both accept any certificate from anyone.

How to confirm the fix

node -e "require('https').get('https://example.com/', r => console.log(r.statusCode)).on('error', e => console.log(e.code))"
# 200
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
# 2

Check from outside as well: the SSL/TLS checker lists every certificate your server sends and shows whether the chain reaches a trusted root.

Related errors

CertificateExpiry, issuer, domains (SAN)
ChainIntermediate and root CA validation
TLS ProtocolTLS version and cipher suite
VulnerabilitiesHeartbleed, POODLE, weak ciphers

Why teams trust us

TLS 1.3
supported
Full
CA chain check
3,158
checks in 30 days
30/14/7
days-to-expiry alerts

How it works

1

Enter domain

2

TLS chain verified

3

Expiry date & vulnerabilities

What Does the SSL Check Cover?

SSL/TLS is the encryption protocol that protects data between the browser and server. Our tool analyzes the certificate, chain of trust, TLS version, and knownvulnerabilities.

Certificate Details

Issuer, validity period, signature algorithm, covered domains (SAN), and validation type (DV/OV/EV).

Chain of Trust

Full chain verification: from leaf certificate through intermediates to root CA.

TLS Analysis

Protocol version (TLS 1.2/1.3), cipher suites, Perfect Forward Secrecy (PFS) support.

Expiry Alerts

Set up a monitor — get Telegram and email alerts 30/14/7 days before expiration.

DV vs OV vs EV Certificates

DV (Domain Validation)
  • Confirms domain ownership only
  • Issued in minutes automatically
  • Free via Let's Encrypt
  • Suitable for most websites
  • Most common certificate type
OV / EV
  • Organization (OV) or Extended Validation (EV)
  • Issued in 1-5 business days
  • Costs $50 to $500/year
  • For finance, e-commerce, government sites
  • Increases user trust

Who uses this

DevOps

SSL certificate monitoring

Security

TLS config audit

SEO

HTTPS as ranking factor

E-commerce

customer trust

Common Mistakes

Expired certificateBrowsers block sites with expired SSL. Set up auto-renewal or monitoring.
Incomplete certificate chainWithout intermediate CA, some browsers and bots cannot verify the certificate.
Mixed content on HTTPS siteHTTP resources on an HTTPS page — the browser lock icon disappears, reducing trust.
Using TLS 1.0/1.1Legacy TLS versions have known vulnerabilities. Use TLS 1.2+ or 1.3.
Domain mismatch in certificateThe certificate must cover all site domains, including www and subdomains.

Best Practices

Set up auto-renewalLet's Encrypt + certbot with cron — certificate renews automatically every 60-90 days.
Enable HSTSStrict-Transport-Security header forces browsers to always use HTTPS.
Use TLS 1.3TLS 1.3 is faster (1-RTT handshake) and safer — legacy ciphers removed.
Monitor expiration datesCreate a monitor on Enterno.io — get notified well before expiration.
Verify chain after renewalAfter certificate renewal, confirm that intermediate certificates are installed.

Get more with a free account

SSL certificate monitoring, check history and alerts 30 days before expiry.

Sign up free

Learn more

Sources

Frequently Asked Questions

Why does Node.js say unable to verify the first certificate when Chrome opens the site?

Chrome downloads a missing intermediate certificate from the address inside the server certificate; Node.js does not. The server is sending only its own certificate and must send the intermediate too.

Does NODE_EXTRA_CA_CERTS fix unable to verify the first certificate?

Only for a private CA you control, by adding its root. For a public site the right fix is on the server: serve the full chain so every client can verify it.

Why did adding the ca option make things worse?

An explicit ca option replaces Node.js built-in root certificates instead of adding to them. Use NODE_EXTRA_CA_CERTS, or include all the roots you need in the ca list.

Is rejectUnauthorized: false acceptable for testing?

Only against a throwaway local server. In any code that might reach production it accepts forged certificates, so fix the chain instead.

How many certificates should the server send?

The leaf plus every intermediate up to, but not including, the root. For most public certificates that is two certificates.

Try the live tool that powered this guide

Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.