SSL_ERROR_BAD_CERT_DOMAIN: Certificate Name Mismatch — Cause and Fix
Fix SSL_ERROR_BAD_CERT_DOMAIN and ERR_CERT_COMMON_NAME_INVALID: diagnose missing SAN entries, wildcard scope, SNI default-vhost fallback, and Ingress host mismatches with openssl.
- #security
- #hardening
- #tls
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
What this error means
The certificate the server presented is structurally valid and signed by a CA the browser
trusts — it is simply not issued for the hostname you asked for. Firefox reports this as
SSL_ERROR_BAD_CERT_DOMAIN; Chrome and Edge call the same condition
NET::ERR_CERT_COMMON_NAME_INVALID.
Firefox: Warning: Potential Security Risk Ahead
websites prove their identity via certificates. Firefox does not trust this
site because it uses a certificate that is not valid for shop.example.com.
Error code: SSL_ERROR_BAD_CERT_DOMAIN
Chrome: Your connection is not private
NET::ERR_CERT_COMMON_NAME_INVALID
The check is narrow and worth knowing precisely: the client compares the hostname in the URL
against the certificate’s Subject Alternative Name (SAN) list. Modern browsers have ignored
the legacy Common Name (CN) field entirely since Chrome 58 (2017), so a certificate whose CN says
shop.example.com but whose SAN list omits it will still fail. This surprises people who
inspect the cert, see the right name in the subject line, and conclude the certificate is fine.
This is distinct from expiry (SEC_ERROR_EXPIRED_CERTIFICATE) and from an untrusted issuer
(SEC_ERROR_UNKNOWN_ISSUER / ERR_CERT_AUTHORITY_INVALID). Those are problems with the
certificate itself. This one is a routing or issuance mismatch — usually the server handed
back the wrong certificate, not a broken one.
Symptoms at the client
- The browser interstitial names the host it expected and the name the certificate covers.
curlfails butcurl -ksucceeds — the transport works, only validation fails.- The apex works and
wwwdoes not, or vice versa. - One subdomain fails while its siblings are fine.
- It fails from outside but works from inside the cluster or over a port-forward.
- It started immediately after a certificate renewal, an Ingress edit, or a new vhost.
- Older clients that still honour CN succeed while current browsers fail.
Name-mismatch causes
- SAN list omits the hostname. The most common cause. The CSR was built with a CN only, or a name was dropped at renewal.
- Apex and
wwwtreated as one name.example.comandwww.example.comare distinct SAN entries. A certificate for one does not cover the other. - Wildcard scope misunderstood.
*.example.commatchesshop.example.combut notexample.comitself and notapi.shop.example.com— a wildcard covers exactly one label. - SNI default-vhost fallback. The client sent a hostname the server has no vhost for, so the
server answered with its default certificate. Very common on nginx when
server_namedoes not match, or when nodefault_serveris defined and the first block wins. - TLS terminated somewhere else. A load balancer, CDN, or service mesh sidecar is terminating TLS with its own certificate before the request ever reaches your origin.
- Ingress host does not match the TLS block. In Kubernetes,
spec.rules[].hostandspec.tls[].hosts[]are separate lists; a name present in one and absent from the other yields the cluster’s fallback certificate. - cert-manager issued a different set of names.
dnsNameson the Certificate drifted from the Ingress host, or the Certificate is not actually Ready and a stale Secret is being served. - Connecting by IP or internal DNS name.
https://10.0.1.15/cannot match a certificate issued for a DNS name unless the IP is in the SAN list as anIP Addressentry.
Inspecting the TLS handshake
Ask the server what it actually presents for that exact hostname. -servername sets SNI and is
the flag that matters here:
openssl s_client -connect shop.example.com:443 -servername shop.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName -dates
Read the X509v3 Subject Alternative Name line, not the subject. If your hostname is absent from
that list, you have found the problem.
Confirm an SNI default-vhost fallback by asking the same socket twice — once with SNI, once without. Two different certificates means the server has no vhost for that name and is falling back to a default:
for sni in shop.example.com ""; do
echo "--- SNI: ${sni:-<none>}"
openssl s_client -connect shop.example.com:443 ${sni:+-servername "$sni"} </dev/null 2>/dev/null \
| openssl x509 -noout -subject
done
Check the whole SAN list at once when you are auditing several names:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -text \
| awk '/Subject Alternative Name/{getline; gsub(/ *DNS:/,"\n "); print}'
Confirm what curl thinks, which reports the mismatch in plain language:
curl -vI https://shop.example.com 2>&1 | grep -Ei 'subjectAltName|SSL certificate problem|issuer'
On nginx, find which block is answering — the resolved config is authoritative, not the file you think is loaded:
nginx -T 2>/dev/null | grep -nE 'server_name|ssl_certificate|listen .*443'
In Kubernetes, compare the two host lists that must agree:
kubectl get ingress shop -o jsonpath='rules: {.spec.rules[*].host}{"\n"}tls: {.spec.tls[*].hosts}{"\n"}'
kubectl get certificate shop-tls -o jsonpath='{.spec.dnsNames}{"\n"}{.status.conditions[?(@.type=="Ready")].status}{"\n"}'
Inspect the certificate actually stored in the Secret, which may lag the Certificate resource:
kubectl get secret shop-tls -o jsonpath='{.data.tls\.crt}' | base64 -d \
| openssl x509 -noout -subject -ext subjectAltName -dates
The fix
Work in this order — it separates “wrong certificate issued” from “right certificate, wrong server answering”, which need different repairs:
- Read the served SAN list with
openssl s_client -servername <host>and confirm the hostname is genuinely absent, rather than assuming from the browser message. - Repeat the handshake without SNI. If a different certificate comes back, the server has no vhost for that name and is serving a default — fix the routing, not the certificate.
- If the name is missing from the SAN list, reissue the certificate with every hostname included, verifying the CSR before submitting it.
- If the certificate is correct, align the server configuration so the hostname maps to the vhost
holding that certificate —
server_namein nginx, or matchingrules.hostandtls.hostsin a Kubernetes Ingress. - Reload the serving process so it re-reads the certificate from disk, then confirm the Certificate or Secret actually updated rather than trusting the control plane’s status.
- Re-validate from outside the network in a private browser window, since TLS state is cached aggressively and a fixed server can still look broken.
Reissue with every name in the SAN list. With an explicit config so nothing is dropped at renewal:
cat > san.cnf <<'EOF'
[req]
distinguished_name = dn
req_extensions = v3_req
prompt = no
[dn]
CN = example.com
[v3_req]
subjectAltName = @alt
[alt]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = shop.example.com
EOF
openssl req -new -key tls.key -out tls.csr -config san.cnf
openssl req -in tls.csr -noout -text | grep -A1 'Subject Alternative Name' # verify before sending
With certbot, list every name in one certificate:
certbot certonly --nginx -d example.com -d www.example.com -d shop.example.com
Fix the nginx vhost so the hostname resolves to the right block, and add an explicit
default_server that fails closed rather than serving the wrong certificate:
server {
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on; # nginx 1.19.4+: refuse unknown SNI instead of guessing
}
server {
listen 443 ssl;
server_name example.com www.example.com shop.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
}
nginx -t && systemctl reload nginx
Align the Kubernetes Ingress host lists — every host under rules must also appear under
tls.hosts:
spec:
tls:
- hosts:
- shop.example.com # must match the rule host exactly
secretName: shop-tls
rules:
- host: shop.example.com
Align cert-manager dnsNames with those hosts, then confirm the Certificate goes Ready before
you retest:
kubectl patch certificate shop-tls --type merge \
-p '{"spec":{"dnsNames":["shop.example.com","www.shop.example.com"]}}'
kubectl wait --for=condition=Ready certificate/shop-tls --timeout=120s
Re-validate from outside, and remember browsers cache TLS state aggressively — test in a private window or restart the browser before concluding the fix failed:
openssl s_client -connect shop.example.com:443 -servername shop.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
Do not “fix” this by clicking through the interstitial, adding a permanent exception, or setting
curl -k / verify=False in application code. The warning is doing exactly its job: telling you
the identity presented does not match the identity requested. Suppressing it in a client removes
the only protection against an interception you would otherwise detect.
Certificate lifecycle
- Monitor SAN coverage, not just expiry. Most alerting checks
notAfterand never notices that a name silently dropped out of the certificate at renewal. - Add new hostnames to the certificate in the same change that adds the DNS record. Name mismatches almost always ship as a two-step deploy where the second step is forgotten.
- Prefer
ssl_reject_handshake onover a catch-all certificate. A clean handshake refusal is far easier to diagnose than a plausible-looking certificate for the wrong name. - Treat wildcards as one label deep and issue explicitly for anything nested.
- Automate renewal end to end, including the reload — a renewed certificate that the process never re-read behaves exactly like a mismatch.
- Blackbox Exporter can assert the served SAN list continuously; pair it with an alert on certificate age so both failure modes are covered.
Frequently asked questions
Why does the certificate look correct in the browser but still fail? Browsers validate the hostname against the Subject Alternative Name list only. The Common Name field shown in most certificate viewers has been ignored by Chrome since version 58 and by Firefox for as long. A certificate whose CN is right but whose SAN list omits the name will always fail.
Does a wildcard certificate cover every subdomain?
No. A wildcard matches exactly one label. *.example.com covers shop.example.com but not
example.com itself and not api.shop.example.com. Nested subdomains need their own SAN entry or
a second wildcard at that level.
Why does it work internally but fail from the internet? Something in front of the origin is terminating TLS — a load balancer, CDN, or ingress controller — and presenting its own certificate. Inside the cluster you reach the origin directly and see the correct one. Check the certificate at each hop rather than only at the origin.
Can I fix this by adding a browser exception?
You can dismiss it, but you should not. The warning means the identity presented does not match
the identity requested, which is precisely the signal that distinguishes a misconfiguration from
an interception. Suppressing it in a browser — or with curl -k or verify=False in code —
removes the only protection you have against the latter.
The certificate resource says Ready but the error persists. What now? Inspect the certificate in the Secret rather than the Certificate resource, and confirm the serving process reloaded. A renewed certificate that nginx or the ingress controller never re-read behaves identically to a name mismatch.
Related TLS errors
- mTLS Error: ‘tls: bad certificate’ Client Authentication Rejected
- Kubernetes Error: ‘tls: bad certificate’
- Security Error: ‘dh key too small’
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.