Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

An error like “untrusted certificate” or “certificate verify failed” does not, by itself, tell you what is wrong. In Node.js, a failed TLS connection falls into one of three groups: the connection never reached a secure state, the server’s certificate chain is not trusted by your connection’s CA configuration, or the certificate chains to a trusted CA but does not name the host you asked for. Each group has a different signal and a different fix. Identify the stage first, then the validation step that failed.

Start with the failure stage

The three problems differ in where they happen. A chain-trust failure and a hostname failure are both authorization results on a peer certificate, while a handshake or setup failure happens before that result exists. The table below separates them.

Problem When it happens Signal in Node.js First thing to check
1. Chain not trusted After the peer certificate is received and checked against the CAs for the connection tlsSocket.authorized is false; tlsSocket.authorizationError describes the reason Whether the issuing CA is the one you intend to trust, and whether it is supplied through the connection’s ca option
2. Certificate does not match the hostname Only after the trust check passes An identity error from the default hostname check, tls.checkServerIdentity(hostname, cert) The host string your client passes, the names on the certificate, and any servername override
3. Handshake or connection setup failed Before a secure connection is established The error arrives without a completed authorization result; the secure connection event is never reached Whether SNI is sent (servername), protocol compatibility, and the exact error code

Record these facts before you change code

The same message can mean different things on different runtimes, so capture the environment first:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • The Node.js version and the bundled OpenSSL version. In a shell, run node -p "process.versions.node + ' / OpenSSL ' + process.versions.openssl".
  • The platform and whether the application runs in a container or on a managed host.
  • The connection API: the https module, or a raw tls.connect() call.
  • The target host and port, and the exact host string passed to the client.
  • The complete error code and message, including any reason, host, or certificate fields attached to the error object.
  • Whether the secureConnect event fired before the error.

Triage in order

  1. Did the connection reach a secure state? If not, treat it as Problem 3 and start with SNI and protocol settings. Do not reason from socket authorization fields, because they describe a completed certificate check.
  2. If a TLS socket exists, read authorized and authorizationError. A false value with a trust-related reason points to Problem 1.
  3. If trust passed but the connection still fails on identity, compare the host string with the certificate’s names. That is Problem 2.
  4. If the certificate looks correct but the wrong one appears, check whether the client sent the intended name in the handshake. A missing name can produce a certificate error that is really a setup error.

Problem 1: the certificate chain is not trusted

A client decides whether the server’s certificate chains to a CA in the trust configuration used by that connection. The Node.js TLS documentation states that tlsSocket.authorized is true when the peer certificate was signed by one of the CAs specified for that socket, and false otherwise. tlsSocket.authorizationError reports the reason.

To see the result for a specific connection, read it inside the secure connection callback:

const tls = require('node:tls');
const fs = require('node:fs');

const socket = tls.connect({
  host: 'internal.example.com',
  port: 443,
  servername: 'internal.example.com',
  ca: fs.readFileSync('/etc/ssl/certs/internal-ca.pem'),
}, () => {
  console.log('authorized:', socket.authorized);
  console.log('reason:', socket.authorizationError);
  socket.end();
});
socket.on('error', (err) => console.error(err.code, err.message));

The ca option in that example is the trust anchor for this connection. Node’s documentation uses the same approach for a self-signed server certificate in a controlled environment: the server certificate is supplied as the CA. Add the CA that issued the certificate you actually expect, after confirming that it is the intended issuer. Do not make the error go away by setting rejectUnauthorized: false; the documentation describes verification against the supplied CAs as the default, and disabling it removes the check that proves the server is who it claims to be. That setting is not a diagnosis.

Problem 2: the certificate does not identify the requested hostname

Trust and identity are separate checks. The tls.checkServerIdentity(hostname, cert) function verifies that the certificate is issued to hostname. The Node.js documentation says this default identity check runs only after other checks, including issuance by a trusted CA, have passed. A certificate can therefore chain to a trusted CA and still fail here because its names do not match the host the client is checking.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check three things:

  • The host string in your client. A load balancer address, an IP address, or an alias will not match a certificate issued for another name.
  • The names on the certificate, including subject alternative names. Look at them with openssl x509 -in server.pem -noout -text and compare them to the host.
  • Any servername override. An override changes the name used for the identity check, so a wrong value can produce this error even when the host is right.

Changing the CA list does not fix a hostname mismatch. Correct the name used for the connection, or request a certificate that names the host you intend to reach. Node records the identity-check failure with its reason, host, and certificate fields, which is the information to compare against the certificate.

Problem 3: the TLS handshake or connection setup failed

A failure can happen before the client has a secure connection and before any authorization result exists. Node’s documentation describes the server-side tlsClientError event as the place where errors that occur before secure establishment are reported. If you operate the server, that event shows handshake failures from clients; if you are the client, the error appears on the socket instead.

The most common setup mistake is SNI. The Node.js documentation states that tls.connect() does not enable Server Name Indication (SNI) by default, unlike the https API. A server that hosts several names may then return a default certificate, or reject the connection. The error you see can then look like a Problem 1 or Problem 2 failure, even though the real mistake is the name missing from the handshake.

With raw tls.connect(), set servername to the intended DNS name when the server selects certificates by name:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const socket = tls.connect({
  host: '203.0.113.10',
  port: 443,
  servername: 'api.example.com', // a DNS name, not an IP address
});

The https module enables SNI for you, so a request that works with https.get() but fails with tls.connect() is a strong hint that the second call is missing servername. If the setup still fails with SNI set, compare protocol settings and the exact error code. The reference does not list every protocol-level failure, so use the code and message from your own runtime rather than a memorized mapping.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Limits of the documented guidance

  • The Node.js TLS documentation does not map every OpenSSL error code to these three categories. Classify by stage and by the authorization or identity field, and treat the code as supporting evidence rather than a label.
  • Which roots are trusted by default can depend on the platform, the Node.js build, and whether a ca option replaces the default set. Confirm the trust result on the machine that fails, using authorizationError, instead of assuming the same trust store everywhere.
  • The behavior described here follows the current Node.js TLS API documentation. Older releases may report errors differently, so record the version as described above.

Use the staged approach above, and keep certificate verification enabled while you work. The fix is almost always a correct CA, a correct name, or a correct handshake, not a switch that turns checks off.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.