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 chainHow 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 / 19self-signed certificate (in the chain).
  • 20missing 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.