openssl s_client, without touching the server config. This guide walks through the checks, in the order a support engineer usually runs them.
How the server decides which certificate to send
Host header yet. It has to pick a certificate without knowing which site the visitor wants.
SNI (Server Name Indication) solves this. It is a field in the first message of the handshake, the ClientHello, where the client says which hostname it is trying to reach. The server reads that name, finds the virtual host (vhost) configured for it on that IP and port, and sends that vhost's certificate.
If no vhost matches the name, or the ClientHello contains no SNI at all, the server falls back to a default. In Apache that is typically the first matching VirtualHost for that IP and port. In Nginx it is the server block marked default_server, or the first one defined for that listen address. LiteSpeed and control panels handle this in their own way, so check how your setup defines the default. Whatever the rule, the result is the same: the visitor receives the certificate of whichever site happens to be the default, and the browser correctly complains that the name does not match.
So a "wrong certificate" usually comes down to one of three things:
- The client sends SNI, but no HTTPS vhost on that IP matches the hostname (missing vhost, missing alias like
www, or the vhost is bound to a different IP). - The client does not send SNI, so it always gets the default certificate.
- DNS points the hostname at a different server or IP than the one you think, so you are looking at someone else's default.
Compare the answer with and without SNI
dig +short example.com first, and note the IP. The examples use 203.0.113.10, a documentation address; replace it and the hostnames with your own.
BASH
# 1) Ask for a specific hostname via SNI
echo | openssl s_client -connect 203.0.113.10:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# 2) Same IP, explicitly no SNI (what an old or misconfigured client sees)
echo | openssl s_client -connect 203.0.113.10:443 -noservername 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltNameecho | closes the connection right after the handshake so the command does not sit waiting for input. Piping into openssl x509 trims the noisy handshake output down to the parts you care about: who the certificate is for (subject and the Subject Alternative Names), who issued it, and when it expires.
One detail trips people up. Since OpenSSL 1.1.1, s_client sends SNI automatically when -connect uses a hostname. So openssl s_client -connect example.com:443 without -servername is not a no-SNI test on a modern system. Use -noservername to force it off, or connect to the IP literal without -servername, since SNI cannot carry an IP address. Older OpenSSL builds behave differently, and -noservername and x509 -ext need 1.1.1 or newer. Run openssl version if the output looks surprising.
Now read the two results side by side:
- With SNI shows the right certificate, without SNI shows another domain. The server is configured correctly. The default certificate simply belongs to another site, which is normal on a shared IP. If a real client still gets the wrong certificate, that client is not sending SNI. Look at the client: an outdated library, a monitoring check, or a script that connects by IP.
- Both show the other domain's certificate. The server has no HTTPS vhost matching that hostname on this IP and port. This is a server-side configuration problem.
- With SNI shows the right certificate for
example.combut not forwww.example.com. The bare domain has a vhost, butwwwis not listed as an alias, or the certificate's SAN list does not include it.
-verify_hostname example.com to the s_client command and look for the Verify return code line in the full output. A mismatch shows up there instead of you having to eyeball the subject.
Test every hostname the server should answer for
BASH
# List every hostname that should be served from this IP
IP='203.0.113.10'
for h in example.com www.example.com shop.example.net mail.example.net; do
printf '%-25s ' "$h"
echo | openssl s_client -connect "IP":443 -servername "h" 2>/dev/null \
| openssl x509 -noout -subject 2>/dev/null || echo 'no certificate returned'
done-ext subjectAltName, because modern clients check the SAN list, not the Common Name in the subject.
The illustrative output format below shows what a mismatch looks like. The third line is the one to investigate.
TEXT
Illustrative output format:
example.com subject=CN = example.com
www.example.com subject=CN = example.com
shop.example.net subject=CN = server1.hostingprovider.test
mail.example.net subject=CN = mail.example.netshop.example.net is getting the server's default certificate. Either it has no SSL vhost on this IP, or the vhost exists but listens somewhere else.
Confirm which virtual host is answering
- On Apache,
apachectl -S(orhttpd -Son some distros) prints every vhost grouped by IP and port, and marks which one is the default for each. If the problem hostname is not listed under the:443section for the IP you tested, that is your answer. - On Nginx,
nginx -Tdumps the full loaded config. Search it for the hostname inserver_nameand check that the matching server block haslisten 443 sslon the right address. Also check which block carriesdefault_server. - On LiteSpeed or a control panel managed server, the generated config layout depends on the setup. Use the panel's SSL or domain view to confirm the certificate is attached to the domain and to the IP the domain resolves to.
VirtualHost 203.0.113.10:443 will not match requests arriving on another address, and the request lands in whatever vhost is default for that other address. Comparing the IP from dig +short with the IP in the vhost definition catches this quickly.
Another pattern is a site that only ever had a port 80 vhost. HTTP works fine, so nobody notices until HTTPS is enabled or a redirect to HTTPS is added.
If the domain sits behind a CDN or proxy, remember that dig returns the proxy's IP, and the certificate you see from the internet comes from the proxy. To test the origin itself, run the same openssl commands against the origin IP directly. curl -vI --resolve example.com:443:203.0.113.10 https://example.com is a useful cross-check too, since curl sends SNI for the hostname while connecting to the IP you specify. If curl and a browser disagree about the same certificate for other reasons, such as the chain, that is a different problem covered in Why curl Rejects an SSL Certificate That Works in Your Browser.
Re-test after the fix
www alias, attach the certificate, or correct the bound IP, reload the web server the normal way for your setup and run the same loop again. Every hostname should now return its own certificate with SNI. The no-SNI test will still return the default certificate. That is expected on a shared IP, not a failure.
If a specific client still gets the wrong certificate after the server checks out, the fix is on the client side. Update the library or tool, make sure it connects by hostname rather than IP, or, if it truly cannot send SNI, give that site a dedicated IP where its certificate is the default.
The habit worth keeping is simple: when someone reports a wrong certificate, do not start by reinstalling it. Run openssl s_client against the real IP once with -servername and once with -noservername. The difference between those two answers tells you whether to look at the vhost config, the DNS, or the client, and the same commands confirm the fix afterwards.