Security Error Guide: 'x509: certificate is valid for X, not Y' Hostname Mismatch
Fix TLS 'certificate is valid for X, not Y' and 'certificate name does not match': diagnose SAN gaps, wrong SNI, missing subjectAltName, and split-horizon DNS.
- #security
- #hardening
- #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.
Overview
A certificate name mismatch means TLS negotiated and the chain verified fine, but the hostname the client connected to is not covered by the certificate’s Subject Alternative Name (SAN) list. The client aborts after validating trust, because presenting a valid certificate for the wrong name is exactly the signal a man-in-the-middle would produce. This is a security control working as designed, not a bug to bypass.
The exact wording depends on the stack:
x509: certificate is valid for api.internal.example.com, not gateway.internal.example.com
curl: (60) SSL: no alternative certificate subject name matches target host name 'gateway.internal.example.com'
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] Hostname mismatch, certificate is not valid for 'gateway.internal.example.com'. (_ssl.c:1000)
This is distinct from an expired certificate or an untrusted issuer — the trust chain is fine. The failure is purely that the requested hostname is absent from the certificate’s SAN entries (modern clients ignore the legacy CN field entirely).
Symptoms
- The connection fails only for one hostname/alias while the “canonical” name works.
- Browsers show
NET::ERR_CERT_COMMON_NAME_INVALIDorSSL_ERROR_BAD_CERT_DOMAIN. - It started after adding a new DNS alias, vhost, or load-balancer name in front of an existing certificate.
- Connecting by IP address fails while connecting by DNS name works (or vice versa).
- Modern clients (Go 1.15+, recent Python/OpenSSL) fail while very old clients that still honor CN succeed.
curl -v https://gateway.internal.example.com/healthz 2>&1 | grep -Ei 'subject|subjectAltName|does not match|matched'
* subject: CN=api.internal.example.com
* subjectAltName does not match gateway.internal.example.com
* SSL: no alternative certificate subject name matches target host name 'gateway.internal.example.com'
Common Root Causes
1. The hostname is simply not in the SAN list
The certificate was issued for one name (or a set of names) and the client is requesting a different alias that was never included.
echo | openssl s_client -connect gateway.internal.example.com:443 -servername gateway.internal.example.com 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:api.internal.example.com, DNS:www.api.internal.example.com
gateway.internal.example.com is not listed, so any client requesting it mismatches.
2. Wrong SNI / a default vhost answering
On a host serving several certificates, a missing or wrong SNI makes the server return its default certificate, which is for a different name.
# without SNI - default cert
echo | openssl s_client -connect 10.0.0.20:443 2>/dev/null | openssl x509 -noout -subject
# with correct SNI - intended cert
echo | openssl s_client -connect 10.0.0.20:443 -servername gateway.internal.example.com 2>/dev/null | openssl x509 -noout -subject
subject=CN = default.internal.example.com
subject=CN = gateway.internal.example.com
If the two differ, the client (or an intermediate proxy) is not sending the SNI the vhost expects.
3. Connecting by IP address or bare hostname
The client targets https://10.0.0.20/ or a short name, but the certificate only lists FQDNs — IP SANs or short names are absent.
curl -v https://10.0.0.20/ 2>&1 | grep -Ei 'subjectAltName|does not match'
* subjectAltName does not match 10.0.0.20
4. Legacy CN-only certificate with no SAN
Older or hand-rolled certificates set only the Common Name and no subjectAltName. Modern TLS clients ignore CN and treat any hostname as a mismatch.
echo | openssl s_client -connect legacy.internal.example.com:443 2>/dev/null \
| openssl x509 -noout -ext subjectAltName || echo "NO SAN EXTENSION"
NO SAN EXTENSION
No SAN means no valid name for modern clients, regardless of the CN.
5. Split-horizon DNS or a wildcard depth mismatch
A wildcard *.example.com does not match a multi-label host a.b.example.com, and split-horizon DNS can point the same name at a server holding a different certificate internally vs externally.
echo | openssl s_client -connect a.b.internal.example.com:443 -servername a.b.internal.example.com 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:*.internal.example.com
*.internal.example.com covers a.internal.example.com but not a.b.internal.example.com — wildcards match a single label only.
Diagnostic Workflow
Step 1: Confirm it is a name mismatch, not trust or expiry
curl -v https://<HOST>/ 2>&1 | grep -Ei 'does not match|expire|issuer|unable to get|self.signed'
Wording about “does not match” / “subject name” is a name mismatch (this guide). “expired”, “self signed”, or “unable to get local issuer” are different problems.
Step 2: Read the certificate’s actual SAN list
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
Compare every name you connect by (FQDN, alias, IP, short name) against the DNS/IP entries printed.
Step 3: Check whether SNI changes which certificate is served
echo | openssl s_client -connect <HOST>:443 2>/dev/null | openssl x509 -noout -subject
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null | openssl x509 -noout -subject
Different subjects mean an SNI/default-vhost problem rather than a missing SAN.
Step 4: Verify what the client is actually requesting
getent hosts <HOST>
curl -v --resolve <HOST>:443:<IP> https://<HOST>/ 2>&1 | grep -Ei 'subject|does not match'
--resolve pins the name to a specific server so you can tell whether split-horizon DNS is sending you to the wrong endpoint.
Step 5: Inspect the server’s certificate-to-vhost mapping
sudo grep -RniE 'server_name|ssl_certificate' /etc/nginx/ | grep -v '#'
Confirm the vhost that matches the requested name points at a certificate whose SAN includes that name.
Example Root Cause Analysis
A new blue/green cutover put a second DNS name, gateway.internal.example.com, in front of an existing API service that already answered on api.internal.example.com. Callers of the new name failed immediately:
x509: certificate is valid for api.internal.example.com, not gateway.internal.example.com
The chain is trusted and unexpired — only the name is rejected — so this is a SAN coverage gap. Reading the served certificate confirms it:
echo | openssl s_client -connect gateway.internal.example.com:443 -servername gateway.internal.example.com 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
subject=CN = api.internal.example.com
X509v3 Subject Alternative Name:
DNS:api.internal.example.com
The certificate lists only the original name. The new alias was added at the DNS/load-balancer layer but never added to the certificate. The correct fix is to reissue the certificate with both names in the SAN list (or issue a dedicated certificate for the new vhost) — never to disable hostname verification on the clients.
# reissue including both names, then verify
echo | openssl s_client -connect gateway.internal.example.com:443 -servername gateway.internal.example.com 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:api.internal.example.com, DNS:gateway.internal.example.com
Both names now resolve to a valid certificate and callers succeed without weakening verification.
Prevention Best Practices
- Enumerate every hostname, alias, and (if needed) IP a service is reached by, and include them all in the SAN list at issuance — treat a new DNS name as a certificate change, not just a DNS change.
- Always populate
subjectAltName; never rely on the deprecated CN field, which modern clients ignore. - Automate issuance and renewal (ACME/cert-manager/internal PKI) driven from the same source of truth as your DNS/ingress config so names cannot drift apart.
- Remember wildcard scope:
*.example.comcovers exactly one label — plan multi-level names or additional wildcards accordingly. - Never “fix” a mismatch by setting
InsecureSkipVerify,curl -k, orverify=False; that disables the exact protection catching a misconfiguration (and would hide a real MITM). See the security hardening guides for a certificate lifecycle baseline. - When a cutover triggers mismatch failures, the free incident assistant can tell a missing-SAN case apart from an SNI/default-vhost or split-horizon DNS cause from the
openssloutput.
Quick Command Reference
# Mismatch vs trust/expiry?
curl -v https://<HOST>/ 2>&1 | grep -Ei 'does not match|expire|unable to get|self.signed'
# What names does the served cert cover?
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
# Does SNI change which cert is served? (default-vhost check)
echo | openssl s_client -connect <HOST>:443 2>/dev/null | openssl x509 -noout -subject
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null | openssl x509 -noout -subject
# Pin the name to a specific server (rule out split-horizon DNS)
curl -v --resolve <HOST>:443:<IP> https://<HOST>/ 2>&1 | grep -Ei 'subject|does not match'
# Find the vhost-to-cert mapping
sudo grep -RniE 'server_name|ssl_certificate' /etc/nginx/ | grep -v '#'
Conclusion
x509: certificate is valid for X, not Y is a hostname-verification failure that fires after the chain has been trusted — the certificate is genuine, just not for the name you asked for. Work it by name coverage:
- The requested hostname is missing from the certificate’s SAN list.
- Wrong or missing SNI makes a default vhost serve a certificate for a different name.
- Connecting by IP or short name that the certificate does not list.
- A legacy CN-only certificate with no SAN, which modern clients reject outright.
- A wildcard that does not cover a multi-label host, or split-horizon DNS routing you to a different certificate.
Read the served certificate’s SAN, compare it to the exact name you connect by, and fix the certificate or the routing — never disable hostname verification to make the error disappear.
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.