What the error says
During the handshake the client checks that the name in the address bar appears in the certificate's Subject Alternative Name (SAN) list. If it does not, every client refuses the connection, and each words it differently:
| Client | Error |
|---|---|
| Chrome, Edge | NET::ERR_CERT_COMMON_NAME_INVALID |
| Firefox | SSL_ERROR_BAD_CERT_DOMAIN |
| curl | SSL: no alternative certificate subject name matches target host name |
| Python | CERTIFICATE_VERIFY_FAILED ... Hostname mismatch, certificate is not valid for |
| Node.js | ERR_TLS_CERT_ALTNAME_INVALID |
| Go | x509: certificate is valid for a.example, not b.example |
| Java | No subject alternative DNS name matching example.com found |
Chrome's name is a leftover. It says "common name", but Chrome has ignored the common name since 2017 and only reads the SAN list. That matters for one of the causes below.
Prove which case you have
Put the name into the SSL/TLS certificate checker. It connects with that name as SNI and shows the certificate the server actually returned: its subject, every SAN, and whether the name matched. Then read the SAN list.
- The SANs are for a different site entirely (a hosting provider's own domain, another customer,
localhost, a CDN default): the server sent the wrong certificate. Skip to the first cause. - The SANs are yours but miss this exact name (you asked for
example.com, it listswww.example.com): the certificate is missing a name. - There are no SANs at all, only a common name: the certificate is CN-only. The certificate chain checker flags this as its own finding.
From a shell, the same comparison with and without SNI shows whether the server picks a certificate by name at all:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName openssl s_client -connect example.com:443 -noservername </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
If the first is right and the second is not, the server is fine for modern clients and the failing client is not sending SNI.
Cause 1: the server sent its default certificate
A server hosting several names picks the certificate from the name the client sends in SNI. When nothing matches, it falls back to a default, and that default is for some other site. In practice this is almost always one of:
- The name is missing from the server block. nginx matches
server_name; Apache matchesServerNameandServerAlias.www.added to the certificate but not to the config gives exactly this error onwwwonly. - IPv6 goes somewhere else. A
listen 443 ssl;without a matchinglisten [::]:443 ssl;means IPv6 visitors land on whichever block does listen there. It fails for some visitors and not others, depending on their network. - DNS points at a platform that does not know the name yet. You pointed a CNAME at a CDN, a PaaS or a SaaS product before adding the custom domain in its dashboard, so it answers with its own certificate. Adding the domain on the platform side fixes it, usually within minutes.
- Old DNS. Some resolvers still hold the address of the previous server, which does not have the new certificate. The DNS trace and propagation checker shows which ones.
- One node in a pool is different. Behind a load balancer or round-robin DNS, one machine was missed in the last deploy. The error comes and goes. Test each address directly with
openssl s_client -connect 203.0.113.10:443 -servername example.com.
Cause 2: the certificate does not cover the name
The server sent your certificate, and it simply does not list the name. Usually it is one of these rules:
- Apex and www are two names. A certificate for
www.example.comdoes not coverexample.com, and the reverse. Request both. - A wildcard covers exactly one label.
*.example.commatchesapi.example.com. It does not matchexample.com, and it does not matchv2.api.example.com. - An IP address is not a hostname. Browsing to
https://203.0.113.10/needs an IP address SAN, which public CAs rarely issue. Use the name. - A new subdomain, an old certificate. Someone added
shop.to DNS and the web server, not to the certificate request. Add it and reissue; with ACME clients that is one more-d.
Cause 3: a certificate with only a common name
Certificates from internal CAs, old appliances and hand-rolled openssl req commands sometimes put the hostname only in the subject's CN and have no SAN extension. Chrome (since version 58), Go (since 1.15), current Python and most other clients no longer look at the CN, so the name is simply not there as far as they are concerned. The fix is to reissue with a SAN. For an internal CA:
openssl req -new -key server.key -subj "/CN=intranet.example.com" \ -addext "subjectAltName=DNS:intranet.example.com" -out server.csr
Then check that the CA's signing profile copies the extension across; some do not by default.
Cause 4: a client that does not send SNI
Very old clients (Java 6, Python 2 before 2.7.9, Android 2, Windows XP) connect without SNI, so a multi-site server can only give them its default. There is nothing to fix on the certificate. Either make this site the default for the address, give it an address of its own, or accept that such clients cannot reach it.
What not to do
Turning off hostname checking (curl -k, verify=False, check_hostname = False) makes the error disappear by accepting any valid certificate for any site, including one an attacker bought for their own domain. It is how interception works. Fix the name on the server; once the checker shows a match for every address, every client agrees.