What it means: The server sent only its own certificate, so the client could not check who signed it.
-showcertsca options in your codeFree online tool — SSL certificate checker: instant results, no signup.
Runs a full TLS check: handshake, protocol versions, certificate chain, trust and expiry.
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.
| Situation | What you see | Fix |
|---|---|---|
Server configured with cert.pem instead of fullchain.pem | Browsers work, Node.js and curl fail | Point the server at the full chain |
| Certificate renewed, chain file not updated | Started on the renewal date | Rebuild the chain from the new intermediate |
| Load balancer or CDN has the leaf uploaded without the chain | Origin is fine, the public hostname fails | Upload the intermediate with the certificate |
Client code passes its own ca option | Only your service fails; curl to the same URL works | Include 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.
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'
1One 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 authorityInfoAccessThe 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.pemPrefer 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.
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; with the leaf first and intermediates after it, then nginx -t && systemctl reload nginx.SSLCertificateFile.ca:NODE_EXTRA_CA_CERTS=/etc/ssl/private-root.pem node app.jsrejectUnauthorized: false or NODE_TLS_REJECT_UNAUTHORIZED=0; both accept any certificate from anyone.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'
# 2Check from outside as well: the SSL/TLS checker lists every certificate your server sends and shows whether the chain reaches a trusted root.
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.
Issuer, validity period, signature algorithm, covered domains (SAN), and validation type (DV/OV/EV).
Full chain verification: from leaf certificate through intermediates to root CA.
Protocol version (TLS 1.2/1.3), cipher suites, Perfect Forward Secrecy (PFS) support.
Set up a monitor — get Telegram and email alerts 30/14/7 days before expiration.
SSL certificate monitoring
TLS config audit
HTTPS as ranking factor
customer trust
www and subdomains.Strict-Transport-Security header forces browsers to always use HTTPS.SSL certificate monitoring, check history and alerts 30 days before expiry.
Sign up freeChrome 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.
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.
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.
Only against a throwaway local server. In any code that might reach production it accepts forged certificates, so fix the chain instead.
The leaf plus every intermediate up to, but not including, the root. For most public certificates that is two certificates.
Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.