Programming languages

Node.js ERR_TLS_CERT_ALTNAME_INVALID: hostname mismatch fixed

By · Last verified

Error messageERR_TLS_CERT_ALTNAME_INVALID

ERR_TLS_CERT_ALTNAME_INVALID means the name or IP address your Node.js code connected to is not listed in the server certificate's Subject Alternative Name extension. The certificate may be trusted and valid; it belongs to a different name. Fix the URL, the SNI name or the certificate. Do not disable verification.

What the error means

The chain is valid, but the certificate does not list the host you connected to. After the chain is verified, Node.js calls tls.checkServerIdentity(hostname, cert) to compare the requested host with the certificate. When nothing matches, it throws an error with code: 'ERR_TLS_CERT_ALTNAME_INVALID', extra reason, host and cert properties, and a message such as:

Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Host: example.com. is not in the cert's altnames: DNS:www.example.com

The part after the colon is the reason. These are the variants Node.js produces:

  • Host: <name>. is not in the cert's altnames: DNS:...: the DNS names in the certificate do not cover the host.
  • IP: <address> is not in the cert's list: ...: you connected to an IP address that is not an IP SAN.
  • Host: <name>. is not cert's CN: ...: the certificate has no DNS SANs, so Node.js fell back to the Common Name, which also did not match.
  • Cert does not contain a DNS name: neither DNS SANs nor a Common Name to compare.

The browser equivalent is NET::ERR_CERT_COMMON_NAME_INVALID; the logic and the fixes are the same.

Causes, most common first

1. The URL uses a name the certificate does not cover

The classic case is https://example.com against a certificate issued only for www.example.com, or a staging host served with the production certificate. Wildcards add another trap: *.example.com covers exactly one label, so it matches neither example.com nor a.b.example.com.

  • Confirm: list the names in the certificate the server presents:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName

Or paste the certificate into the certificate decoder.

  • Fix: use a covered name, or reissue the certificate with every name you serve, for example certbot certonly -d example.com -d www.example.com.

2. The server returns its default certificate because of SNI

Servers with many sites pick the certificate by the SNI name in the ClientHello. When the client connects by IP, through a tunnel, or with a custom DNS lookup, and does not send the right SNI, it gets the default site's certificate.

  • Confirm: compare the certificate with and without -servername in openssl s_client; different subjects mean SNI decides.
  • Fix: pass the real name explicitly:
const tls = require('node:tls');
const socket = tls.connect({ host: '203.0.113.10', port: 443, servername: 'example.com' }, () => {
  console.log('authorized:', socket.authorized);
  socket.end();
});

3. Connecting by IP address

https://203.0.113.10/ fails unless the certificate has that IP as an IP SAN. Internal services reached by IP, Docker or Kubernetes service names (https://api:8443) and localhost hit this often.

  • Fix: call the service by a DNS name that is on the certificate, or issue the internal certificate with the IP or service name in its SAN list.

4. A CDN, load balancer or hosting platform serves the wrong certificate

A custom domain pointed at a CDN, object storage bucket or PaaS before its certificate was uploaded returns the platform's own certificate, whose SANs are the platform's domains.

  • Confirm: the SAN list shows the provider's domains instead of yours.
  • Fix: upload or provision the certificate for your domain in the provider's console, then retest.

5. The certificate was not updated after adding a domain

The server still serves an older certificate that predates the new name. Renew or reissue with the full name list and reload the server.

Fix commands by platform

Every fix makes the host name the client checks match a SAN in the certificate the server sends.

Linux / macOS (check what the server sends):

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName

Windows PowerShell (same check with Node.js):

node -e "const s=require('tls').connect(443,'example.com',{servername:'example.com'},()=>{console.log(s.getPeerCertificate().subjectaltname);s.end()})"

Calling an IP but verifying the real name (keeps full verification):

const https = require('node:https');
const tls = require('node:tls');
https.get({
  host: '203.0.113.10',
  servername: 'internal.example.com',
  headers: { Host: 'internal.example.com' },
  checkServerIdentity: (_host, cert) => tls.checkServerIdentity('internal.example.com', cert),
}, res => console.log(res.statusCode));

Nginx serving several sites: make sure each server block has the right server_name and its own ssl_certificate, and that the default server does not catch your domain.

Don't disable certificate verification

Each switch that hides this error removes protection against impersonation. rejectUnauthorized: false, NODE_TLS_REJECT_UNAUTHORIZED=0 and checkServerIdentity: () => undefined all make the error go away. The first two accept any certificate at all; the third keeps chain validation but drops the name check, which is equally bad: any attacker who owns any trusted certificate for any domain can then impersonate your server and read tokens, passwords and API responses.

The safe alternatives are specific: connect with the right name, set servername so SNI selects the right certificate, issue certificates with every name you use, and when you must dial an IP, verify against the expected hostname with tls.checkServerIdentity(expectedName, cert) as shown above.

Verify the fix

node -e "require('https').get('https://example.com', r => { console.log(r.statusCode, r.socket.authorized); r.resume(); }).on('error', e => console.error(e.code, e.reason || e.message))"

It should print a status code and true. Also run both the bare and www names through the SSL checker to confirm each one matches, and read about Subject Alternative Names if you are planning which names to put on a new certificate.

FAQ

Does a wildcard certificate for *.example.com cover example.com?

No. A wildcard matches exactly one label in the leftmost position, so *.example.com covers api.example.com but neither example.com nor v1.api.example.com. Put the bare domain in the certificate as a separate name, or request a certificate for the deeper name.

Can I connect to an IP address over HTTPS?

Only if the certificate contains that IP address as an IP subjectAltName; Node.js then reports a different reason, IP: 203.0.113.10 is not in the cert's list, when it does not. Most public certificates contain only DNS names, so connect by name, or use an internal CA that issues certificates with IP entries.

Is a custom checkServerIdentity safe?

Only if it still verifies. Returning undefined unconditionally disables the hostname check, so any trusted certificate for any domain would be accepted. The safe pattern is to call tls.checkServerIdentity(expectedName, cert) with the name you expect, for example when you dial an IP address but know the service's real hostname.

Why does Go reject a certificate that Node.js accepts?

Node.js falls back to the subject Common Name when a certificate has no DNS subjectAltName entries. Go 1.15 and later ignore the Common Name, so a CN-only certificate fails there; see Go x509 legacy Common Name. Reissue such certificates with SANs so all clients agree.

Sources

  1. Node.js docs: Errors — ERR_TLS_CERT_ALTNAME_INVALID
  2. Node.js docs: tls.checkServerIdentity() and tls.connect() servername
  3. Node.js source: lib/tls.js (checkServerIdentity)
  4. RFC 9525: Service Identity in TLS