Short answer
Use openssl s_client -connect HOST:443 -servername HOST as your starting point. The output tells you the negotiated protocol and cipher, shows the certificate chain the server sent, and gives a Verify return code that points straight at the cause. Almost every error we cover in this series can be diagnosed with variants of this one command.
The base command
openssl s_client -connect example.com:443 -servername example.com
-servername sends SNI so you hit the right certificate on multi-domain servers — never omit it. Prefix with echo | (or append </dev/null) to close the connection automatically instead of hanging.
Read the four key lines
| Line | What it tells you |
|---|---|
Protocol : | The negotiated TLS version (e.g. TLSv1.3) |
Cipher : | The chosen cipher suite |
Certificate chain | How many certificates the server sent (the chain) |
Verify return code: | 0 = OK; anything else is a validation error |
Recipes for specific problems
# Force a specific protocol (test whether 1.0/1.1 is still open) openssl s_client -connect example.com:443 -tls1_2 openssl s_client -connect example.com:443 -tls1_3 # See the full chain the server sends (count the certificates) openssl s_client -connect example.com:443 -servername example.com -showcerts # Read expiry dates, subject and issuer echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -dates -subject -issuer # See which names (SAN) the certificate covers echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -text | grep -A1 "Subject Alternative Name" # Validate against a specific root (internal CA / inspection proxy) openssl s_client -connect internal.example.com:443 -CAfile corporate-root.crt # Send a client certificate (mTLS) openssl s_client -connect api.example.com:443 -servername api.example.com \ -cert client.crt -key client.key
Verify return codes you will meet
- 0 — OK.
- 10 — the certificate has expired.
- 18 / 19 — self-signed certificate (in the chain).
- 20 — missing issuer (intermediate not sent).
- 21 — unable to verify the first certificate (often the same root cause as 20).
Other tools in the same kit
For ciphers, nmap --script ssl-enum-ciphers -p 443 host and testssl.sh are faster than guessing. For a polished report, SSL Labs works on public hosts. But for the daily "why does this one connection fail", s_client is still king — see also the handshake-failure guide.
From one-off debugging to continuous monitoring
s_client is perfect when you already know which host is misbehaving. It just does not scale to hundreds of endpoints. CertControl runs the same kind of check — protocol, cipher, chain, expiry — continuously across all your endpoints and raises a finding when something drifts, so you do not have to run the command manually before a user notices the failure.
Frequently asked questions
Why does openssl s_client hang without returning?
Because the connection is held open waiting for input. Type echo | openssl s_client ... or append </dev/null so it closes automatically after the handshake.
What exactly does -servername do?
It sends SNI (Server Name Indication) so the server knows which domain you are asking for. Without it you get the default certificate on multi-domain servers — a frequent source of confusion.
Can I use s_client to test a mail server?
Yes. Use -starttls smtp (or imap/pop3) with the relevant port, e.g. -connect mail.example.com:587 -starttls smtp.
What does Verify return code: 0 mean?
That the client could build a valid chain from the server's certificate up to a trusted root, and the certificate is valid for the name. In other words: validation succeeded.