NET::ERR_CERT_COMMON_NAME_INVALID and ERR_CERT_DATE_INVALID (tested)
NET::ERR_CERT_COMMON_NAME_INVALID means the certificate is not valid for the name in the address bar. NET::ERR_CERT_DATE_INVALID means it is expired or not valid yet. We built each broken certificate from a private test root, made every client trust that root, and recorded the exact error each one gave.
1. What the two errors mean
Chromium checks two things about the server certificate besides who signed it. First, the host name (or IP) you typed must be listed in the certificate's Subject Alternative Name (SAN). If not, you get NET::ERR_CERT_COMMON_NAME_INVALID. The name is old: Chromium no longer looks at the Common Name at all. Second, the current time must be between notBefore and notAfter. If not, you get NET::ERR_CERT_DATE_INVALID, for both expired and not-yet-valid certificates.
If the root is not trusted, Chromium reports NET::ERR_CERT_AUTHORITY_INVALID instead. We saw exactly that when we ran the same test without the temporary trust store. See ERR_CERT_AUTHORITY_INVALID for that case.
2. Our test setup
We made one private root CA and signed a leaf certificate for each case with openssl x509 -req. A Node.js HTTPS server on 127.0.0.1 served one certificate at a time. Chromium trusted the root through a temporary NSS database (certutil -A -t "C,,") in a temporary HOME, so no system store was touched. Wildcard names were mapped to 127.0.0.1 with --host-resolver-rules in Chromium and --resolve in curl.
openssl x509 -req -in leaf.csr -CA root.pem -CAkey root.key \
-extfile san.ext -days 90 -out leaf.pem
# san.ext: subjectAltName=DNS:localhost,IP:127.0.0.1
# expired: -not_before 20250101000000Z -not_after 20260101000000Z
3. Results: name errors
| Case | Chromium 153 | Node fetch | curl | Python |
|---|---|---|---|---|
| CN=localhost, no SAN | ERR_CERT_COMMON_NAME_INVALID | accepted (200) | accepted | accepted |
| SAN DNS:other.test, CN=localhost, URL localhost | ERR_CERT_COMMON_NAME_INVALID | ERR_TLS_CERT_ALTNAME_INVALID | (60) no alternative certificate subject name matches | Hostname mismatch |
| SAN DNS:localhost only, URL https://127.0.0.1 | ERR_CERT_COMMON_NAME_INVALID | ERR_TLS_CERT_ALTNAME_INVALID | (60) ... target ipv4 address '127.0.0.1' | IP address mismatch |
| SAN *.example.test, URL example.test | ERR_CERT_COMMON_NAME_INVALID | ERR_TLS_CERT_ALTNAME_INVALID | (60) | Hostname mismatch |
| SAN *.example.test, URL a.example.test | 200 | 200 | ok | ok |
| SAN *.example.test, URL a.b.example.test | ERR_CERT_COMMON_NAME_INVALID | ERR_TLS_CERT_ALTNAME_INVALID | (60) | Hostname mismatch |
The surprise is the first row. A certificate with only a Common Name and no SAN worked in curl, Node and Python, which still fall back to the CN when there is no SAN. Chromium rejected it. So "it works with curl" does not prove the certificate is fine for browsers.
The second row shows the reverse. The CN said localhost, but once a SAN was present every client ignored the CN and failed.
The exact messages we got:
Chromium: net::ERR_CERT_COMMON_NAME_INVALID at https://localhost:8994/
Node: TypeError: fetch failed
cause: ERR_TLS_CERT_ALTNAME_INVALID - Hostname/IP does not match
certificate's altnames: Host: localhost. is not in the cert's altnames: DNS:other.test
Node (IP): Hostname/IP does not match certificate's altnames: IP: 127.0.0.1 is not in the cert's list:
curl: curl: (60) SSL: no alternative certificate subject name matches target hostname 'localhost'
Python: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch,
certificate is not valid for 'localhost'. (_ssl.c:1029)
4. Results: date errors
| Case | Chromium 153 | Node fetch (cause.code) | curl | Python |
|---|---|---|---|---|
| Expired (notAfter 2026-01-01) | ERR_CERT_DATE_INVALID | CERT_HAS_EXPIRED "certificate has expired" | (60) SSL certificate problem: certificate has expired | certificate verify failed: certificate has expired |
| Not yet valid (notBefore 2027-01-01) | ERR_CERT_DATE_INVALID | CERT_NOT_YET_VALID "certificate is not yet valid" | (60) SSL certificate problem: certificate is not yet valid | certificate verify failed: certificate is not yet valid |
| 825-day validity, private root | 200 | 200 | ok | ok |
| Expired and wrong name | ERR_CERT_COMMON_NAME_INVALID | CERT_HAS_EXPIRED | certificate has expired | Hostname mismatch |
Chromium uses one error for both directions. Node and curl tell you which one. A not-yet-valid certificate on a correct server usually means the client clock is wrong, or a certificate was installed with a future start time.
When both problems exist, clients disagree on which one they report. Fixing the first error may reveal the second, so check both with openssl (section 5).
Validity length: our 825-day certificate from a private root loaded in Chromium. Per the CA/Browser Forum and Chrome root program docs (not measured), certificates from public CAs issued after 1 September 2020 must be valid for 398 days or less, and browsers reject longer ones from those CAs. Private roots you add yourself are not held to that rule in our test.
5. Check the certificate with openssl
Look at the certificate the server really sends (not the file you think it uses):
$ echo | openssl s_client -connect 127.0.0.1:8994 -servername localhost -CAfile root.pem 2>/dev/null \
| openssl x509 -noout -subject -dates -ext subjectAltName
subject=CN=localhost
notBefore=Jan 1 00:00:00 2025 GMT
notAfter=Jan 1 00:00:00 2026 GMT
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1
A certificate with no SAN prints this. Chromium will reject it with ERR_CERT_COMMON_NAME_INVALID:
$ openssl x509 -in cnonly.pem -noout -ext subjectAltName
No extensions in certificate
Test a name, an IP and the expiry directly:
$ openssl x509 -in mismatch.pem -noout -checkhost localhost
Hostname localhost does NOT match certificate
$ openssl x509 -in good.pem -noout -checkhost localhost -checkip 127.0.0.1
Hostname localhost does match certificate
IP 127.0.0.1 does match certificate
$ openssl x509 -in expired.pem -noout -checkend 0
Certificate will expire # exit code 1
$ openssl x509 -in good.pem -noout -checkend 2592000
Certificate will not expire # exit code 0, still valid in 30 days
Watch out: -checkhost said "does match" for the CN-only certificate, because OpenSSL falls back to the CN. Use -ext subjectAltName to see what Chromium will see.
Another trap: s_client does not check the host name unless you ask. Against the wrong-name certificate it said Verify return code: 0 (ok). With -verify_hostname localhost it said Verify return code: 62 (hostname mismatch). For the expired one it said Verify return code: 10 (certificate has expired).
6. Fixes
- No SAN. Reissue with a SAN. With openssl:
-addext "subjectAltName=DNS:example.com,DNS:www.example.com"onreq, orsubjectAltName=...in the-extfileforx509 -req. Public CAs such as Let's Encrypt always add a SAN. - Wrong name. List every name users type.
www.example.comandexample.comare two names. Check that the server picks the right certificate for the SNI name (on a shared IP the default certificate is often someone else's). - IP address URL. A DNS entry never matches an IP. Add
IP:203.0.113.10to the SAN, or use a host name. Public CAs rarely issue IP certificates, so a name is the usual fix. - Wildcard.
*.example.comcovers exactly one label. It does not coverexample.comora.b.example.com. Add the apex as its own SAN entry; add*.b.example.comfor deeper names. - Expired. Renew and reload the server. Then confirm with
s_clientthat the new certificate is served. A renewed file on disk does nothing until nginx, Apache or Node reloads it. Set up monitoring: see check SSL certificate expiry. - Not yet valid. Check the client clock first (
date -u). If the clock is right, the certificate was issued with a futurenotBefore; reissue it.
Do not "fix" these by turning off verification (curl -k, rejectUnauthorized: false, verify=False). That accepts any certificate for any name. For other TLS failures before the certificate stage, see ERR_SSL_PROTOCOL_ERROR.
Frequently asked questions
Why does curl work but Chrome shows ERR_CERT_COMMON_NAME_INVALID?
Most likely the certificate has no Subject Alternative Name. In our test curl, Node and Python accepted a CN-only certificate because they fall back to the Common Name. Chromium 153 ignored the CN and failed. Run openssl x509 -noout -ext subjectAltName; if it prints 'No extensions in certificate', reissue with a SAN.
Does a wildcard certificate cover the bare domain?
No. A certificate for *.example.test failed for example.test and for a.b.example.test in all four clients we tested. It only worked for a.example.test. Add example.test as a separate SAN entry.
What is the difference between ERR_CERT_DATE_INVALID and CERT_HAS_EXPIRED?
They are the same problem in different clients. Chromium shows ERR_CERT_DATE_INVALID for both expired and not-yet-valid certificates. Node reports CERT_HAS_EXPIRED or CERT_NOT_YET_VALID, and curl says 'certificate has expired' or 'certificate is not yet valid'.
Can I use an IP address with a certificate made for a host name?
No. A certificate with only DNS:localhost failed for https://127.0.0.1 in every client, while it worked for https://localhost. The SAN needs an IP Address entry for that exact IP.
Does Chrome reject certificates valid for more than 398 days?
Per the CA/Browser Forum and Chrome docs (not measured), that limit applies to certificates from public CAs. An 825-day certificate from our own private root loaded fine in Chromium 153, curl, Node and Python.
Written with AI assistance. Measured on 2026-10-02 with headless Chromium 153.0.8010.12 (Playwright), Node.js 24.21.0 (OpenSSL 3.5.8), curl 8.14.1 (OpenSSL 3.5.7), Python 3.13.5 (OpenSSL 3.5.7) and the openssl 3.5.7 CLI. All certificates were signed by a private test root that each client was told to trust (Chromium via a temporary NSS database under a temporary HOME, curl via --cacert, Node via NODE_EXTRA_CA_CERTS or ca, Python via cafile). The 398-day rule for public CAs is from the CA/Browser Forum and Chrome root program docs, not measured. PlainHeaders is our extension; curl, openssl, Python, Node.js and Chromium are free.