"Unable to verify the first certificate": when Node's --use-system-ca hint won't help
TL;DR. Node's "unable to verify the first certificate" error now ends with a hint to try --use-system-ca. The flag helps when a root CA is missing and installed in the system store. It cannot help when the server leaves
TL;DR. Node's "unable to verify the first certificate" error now ends with a hint to try
--use-system-ca. The flag helps when a root CA is missing and installed in the system store. It cannot help when the server leaves out an intermediate: I tested a host with a missing intermediate and it failed the same way with the flag on, hint included. Check what the server sends first.
To fix βunable to verify the first certificateβ in Node.js, inspect the chain the remote server sends and the CA certificates trusted by the failing process. A public server may be missing an intermediate; an internal service or inspecting proxy may require an approved private CA. Keep TLS verification enabled while fixing that chain or trust configuration.
The certificate chain guide explains the leaf, intermediate and root roles. This walkthrough focuses on identifying which part Node.js cannot verify.
Capture the error code from the failing process
The message alone can hide useful detail. Reproduce the connection inside the same container, VM or runtime environment that fails, with the same destination and proxy configuration.
For a direct HTTPS request using Node's standard library:
import https from "node:https";
https.get("https://api.example.com/health", response => {
console.log("status:", response.statusCode);
response.resume();
}).on("error", error => {
console.error("code:", error.code);
console.error("message:", error.message);
process.exitCode = 1;
});
Replace the example URL with your endpoint. This small script isolates a direct Node HTTPS connection; it does not automatically reproduce an application library's custom agent or proxy. Compare those settings if the script succeeds while your application fails.
Node's certificate error reference distinguishes these failures:
| Error | What to investigate |
|---|---|
UNABLE_TO_VERIFY_LEAF_SIGNATURE |
A lone, non-self-signed leaf with no verifiable issuer in the presented chain |
UNABLE_TO_GET_ISSUER_CERT_LOCALLY |
An issuer that the verifier cannot find locally |
DEPTH_ZERO_SELF_SIGNED_CERT |
A self-signed leaf that this client does not trust |
ERR_TLS_CERT_ALTNAME_INVALID |
A name mismatch, a separate problem from building the chain |
For the last case, use the hostname mismatch checklist. Installing extra CA certificates does not make a certificate cover an unrelated hostname.
When Node suggests --use-system-ca
Since Node 23.11, some of these errors end with a hint:
unable to verify the first certificate; if the root CA is installed locally, try running Node.js with --use-system-ca
Node trusts its bundled CA list by default, not the certificates installed on the machine. The --use-system-ca flag adds the system store. If the missing root CA is installed there, that is the fix:
node --use-system-ca app.mjs
From Node 24.6 and 22.19, NODE_USE_SYSTEM_CA=1 does the same job where you can set an environment variable but not the command.
The hint does not mean the flag applies. A lone leaf gives the same error whether the missing piece is the root or an intermediate, so Node suggests the flag either way. The flag only changes which roots Node trusts. It cannot supply an intermediate the server left out. On Node 26.10, incomplete-chain.badssl.com, a public test host with a missing intermediate, still fails with UNABLE_TO_VERIFY_LEAF_SIGNATURE when the flag is set, and the message still suggests the flag.
Check what the server sends before changing how the process starts.
Inspect the certificates the server actually sends
Run this from the failing environment if OpenSSL is available:
openssl s_client -connect api.example.com:443 \
-servername api.example.com -showcerts </dev/null
Read the certificate list and verification output separately. OpenSSL's s_client documentation describes -showcerts as the list sent by the server. It is not a reconstructed, verified chain.
Copy only the BEGIN CERTIFICATE through END CERTIFICATE blocks into the certificate decoder. It shows each supplied certificate's subject, issuer and dates, in pasted order. Check whether the leaf's issuer corresponds to the next certificate's subject. Matching those names is a useful clue, but it does not verify the signature or prove trust.
The public SSL checker offers another view of what a public host serves. It cannot reproduce a private network or your company's HTTPS inspection path.
If you control the public server, send its intermediates
For a public certificate, the usual deployment is the leaf first, followed by the required intermediate certificates. The client supplies a trusted root. Sending a root certificate from the server does not make the client trust it.
On an Nginx deployment using Certbot's standard file layout, the certificate directive normally points to the full chain:
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
Use your actual paths. Nginx's certificate-chain documentation describes the combined certificate file and its order. A load balancer or managed certificate service has a different configuration surface; fix the certificate at the place that terminates TLS.
After validating the configuration and deploying it through your normal process, repeat the request from the failing Node environment. Also inspect the public endpoint again. Reading a corrected file on disk does not establish that the running listener loaded it.
If the service uses a private CA, configure deliberate trust
An internal CA may be absent from Node's default roots. An HTTPS inspection proxy can also present certificates signed by your organization's CA. Compare the observed issuer with the expected infrastructure, and obtain the approved CA certificate from the people who administer it.
Node supports extending its roots at startup:
NODE_EXTRA_CA_CERTS=/etc/company/approved-root-ca.pem node app.mjs
The NODE_EXTRA_CA_CERTS documentation specifies that the file contains trusted PEM certificates, is read when Node starts, and does not update when you change process.env later. Restart the actual application process with that setting.
Also inspect any explicitly configured ca option in your HTTPS agent or TLS connection. Node documents that an explicit ca replaces the normal and extra CA certificates for that connection. Adding an environment variable or --use-system-ca will not override it.
Do not use a random certificate copied from the failed connection as a new trust anchor. Establish the issuer you intend to trust and fix the server's intermediate chain independently.
Compare browsers and Node without assuming either is wrong
A browser success does not prove that every client can build the same path. Your browser and Node process may use different root stores, cached intermediates, network routes or proxy certificates.
Compare four things: the actual hostname, the leaf fingerprint, the sent intermediate list and the configured trust roots. If the fingerprint or issuer differs between your laptop and the container, investigate the path before rewriting certificate validation code.
Keep rejectUnauthorized enabled. Setting it to false, or disabling verification process-wide, removes the check whose failure you are investigating and can accept an unintended peer.
Verify the fix from the environment that failed
Repeat the original application request after the server or trust configuration changes. Confirm that the error disappears with normal verification settings.
Then follow the certificate monitoring walkthrough. An expiry check gives advance notice of dates. An HTTPS check with TLS verification enabled catches validation failures from its own network and trust context. A private CA path still needs a check that can reach and trust that environment.
Where Uptimepage fits
I build Uptimepage, an uptime monitor that checks your services from several regions and alerts you when one fails. Its HTTP checks verify TLS unless you turn that off, and a separate TLS certificate check warns you before a certificate expires. The certificate decoder and SSL checker linked above are free tools on the same site. It is open source under AGPL, so it can also run on your own server.
Whatever you use, run the openssl s_client command above against your own API today and count the certificates it sends.
Now the question for the comments: the last time you hit this error, was it a missing intermediate, a private CA or a proxy? I would like to hear what it turned out to be, and how long it took to find.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.