Solving ERR_TLS_CERT_ALTNAME_INVALID: Why Node.js Rejects Your SSL Certificate

intermediate🔒 SSL/TLS2026-05-25| Node.js (LTS/Current), OpenSSL 1.1.1+, Linux/macOS/Windows

Error Message

Error: Hostname/IP does not match certificate's altnames: Host: api.example.com is not in the cert's altnames: DNS:example.com
#nodejs#tls#openssl#ssl-certificate#backend#security

Dealing with Hostname MismatchesI ran into a roadblock recently while configuring a local Docker-compose environment for a microservices project. One internal service attempted to fetch data from another over HTTPS, and Node.js immediately killed the connection with this error:

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

Node.js triggers the ERR_TLS_CERT_ALTNAME_INVALID error for your protection. Think of it as a security handshake. The client verifies that the server you are connecting to is actually authorized to use that specific hostname. If you request api.example.com but the certificate only covers example.com, Node.js aborts the connection to block potential man-in-the-middle attacks.

Why modern environments reject your certHistorically, browsers and runtimes relied on the Common Name (CN) field to verify identity. That is no longer the case. Modern systems like Node.js and Chromium-based browsers now require the Subject Alternative Name (SAN) extension. If your target hostname isn't explicitly listed in that SAN array, the connection fails—even if the CN matches perfectly.

You will likely encounter this in three common scenarios:

  • Local Development: Using self-signed certificates for localhost or custom .local domains.- Subdomain Creep: Adding api. or staging. to your infrastructure without re-issuing the certificate.- IP Direct Access: Connecting via 192.168.1.50 when the certificate only validates DNS names.## The "Temporary" Fix You Should AvoidStack Overflow is full of advice suggesting you just flip a global switch to bypass security. You might see this snippet recommended:
// NEVER use this in a production environment
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

Or this one in your request logic:

const agent = new https.Agent({
  rejectUnauthorized: false
});

Don't do it. This effectively strips the "S" out of HTTPS by disabling all certificate validation. It leaves your traffic wide open to interception. Use it for a 10-second diagnostic check if you must, but the real solution lies in the certificate itself.

Step 1: Generate a Certificate with SANWhen using OpenSSL for development, you must explicitly define the SAN extension. I prefer using a configuration file to keep the process repeatable and less prone to typos.

1. Create a config file (server.conf)Create a file named server.conf. Replace api.example.com with your local service name. Including 127.0.0.1 and localhost is a good practice for dev environments.

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = api.example.com

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = api.example.com
DNS.2 = localhost
IP.1 = 127.0.0.1

2. Generate the key and certificateRun this command to generate a 2048-bit RSA key and a 365-day certificate using your configuration:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout server.key \
  -out server.crt \
  -config server.conf \
  -extensions v3_req

This produces server.crt. Unlike a standard self-signed certificate, this one contains the metadata Node.js needs to verify the identity of api.example.com.

Step 2: Custom Verification for Edge CasesSometimes you are stuck with an IP-based connection in a legacy environment, but the certificate is locked to a domain name. If you cannot re-issue the certificate, you can use checkServerIdentity to manually bridge the gap without nuking your security.

This function lets you define exactly how Node.js should validate the server's identity:

const https = require('https');
const fs = require('fs');
const tls = require('tls');

const options = {
  hostname: '10.0.0.5', 
  port: 443,
  ca: fs.readFileSync('server.crt'), 
  checkServerIdentity: (host, cert) => {
    // If we are hitting our internal IP, manually allow it for this cert
    if (host === '10.0.0.5') {
       return undefined; // Validation successful
    }
    // Otherwise, use the standard Node.js logic
    return tls.checkServerIdentity(host, cert);
  }
};

Returning undefined tells Node.js the identity is verified. If the identity is incorrect, the function should return an Error object to terminate the connection.

Verification: Don't Guess, InspectBefore restarting your Node.js application, inspect the certificate's raw data. It takes five seconds and prevents hours of debugging.

openssl x509 -in server.crt -text -noout

Scroll down to the X509v3 Subject Alternative Name section. You should see a list similar to this:

X509v3 extensions:
    X509v3 Subject Alternative Name:
        DNS:api.example.com, DNS:localhost, IP Address:127.0.0.1

If that specific DNS: or IP Address: entry is missing, Node.js will continue to reject the connection with the ERR_TLS_CERT_ALTNAME_INVALID error.

Summary Checklist- Wildcards: Use DNS.1 = *.example.test to cover all subdomains in your local development cluster.- CA Chain: If you get UNABLE_TO_VERIFY_LEAF_SIGNATURE after fixing the SAN, ensure you've passed your root certificate to the ca option in your Node.js request.- OS Trust: On macOS, you may need to drag server.crt into Keychain Access and set it to "Always Trust" for local browsers to stop complaining.

Related Error Notes