Diagnosing TLS Handshake Failures with OpenSSL s_client
Use OpenSSL s_client to perform a live TLS handshake, inspect protocol, cipher, and certificate chain, and isolate handshake failures with version‑forcing and SNI flags.
18 Jul 2025, 08:06 UTC

Why s_client is the go‑to tool
\nWhen a TLS connection fails, the fastest way to see what the server actually sent is to perform a live handshake from the command line. openssl s_client does exactly that: it connects, negotiates protocol and cipher, prints the certificate chain, and returns the verification result. Because it shows the raw server output, you can distinguish a certificate problem from a protocol‑version block or a middle‑box interference.
Running a basic handshake
\nThe simplest invocation targets a host and port, optionally setting the Server Name Indication (SNI) hostname with -servername. Omitting SNI on a shared host often returns the wrong default certificate, making a valid setup appear broken.
# Replace example.com and 443 with your target\nopenssl s_client -connect example.com:443 -servername example.com \nThe command waits for the handshake, then prints a block of information followed by the server’s certificate(s). If you only need the chain, add -showcerts. To make verification failures produce a non‑zero exit code (useful in scripts), add -verify_return_error.
Interpreting the output
\nKey lines to look for:
\n- \n
- Protocol – e.g.,
TLSv1.3orTLSv1.2shows what version was negotiated. \n - Cipher – the symmetric cipher suite selected. \n
- Verify return code –
0 (ok)means the chain trusted by the local store; any other number indicates a verification problem. \n - Certificate chain – each certificate printed with subject and issuer; the leaf is first. \n
If the handshake succeeds but the verify code is non‑zero, the issue is likely a missing intermediate or a self‑signed certificate not present in $OPENSSLDIR/certs (or the system trust store). If the handshake fails before any certificate is printed, look at the error line (e.g., handshake failure) and consider protocol version or SNI mismatches.
Forcing protocol versions and SNI
\nTo isolate whether a failure is due to an unexpected downgrade or a middlebox blocking newer versions, force a specific TLS version:
\n# Test TLS 1.2 only\nopenssl s_client -connect example.com:443 -servername example.com -tls1_2 \nIf the forced version fails while the default succeeds, the server or a network device is likely rejecting that version. Conversely, if both fail, the problem is probably elsewhere (certificate, SNI, or ALPN).
\nLimitations and practical verification
\nTrust store dependence: s_client verifies the chain against the local trust store by default. A self‑signed or private‑CA certificate will show a verification error even though the encrypted channel works. To ignore verification for pure connectivity testing, add -verify 0.
Version‑specific defaults: OpenSSL 3.x may use a different default OPENSSLDIR or security policy than 1.1.1, leading to verify error:num=20:unable to get local issuer certificate after an OS upgrade. Run openssl version -a to confirm the binary’s build paths and default directories.
Checking you inspected the right endpoint: Compare the leaf certificate’s fingerprint from s_client with a local copy:
# Extract the leaf certificate from s_client output (first CERTIFICATE block)\nopenssl s_client -connect example.com:443 -servername example.com -showcerts /dev/null \\\n | openssl x509 -outform PEM | openssl x509 -noout -fingerprint -sha256\n\nThen run the same fingerprint command on the certificate you believe should be served. Matching fingerprints confirm you are debugging the correct host.
\nActionable checklist
\n- \n
- Run
openssl version -ato note the build and default trust store. \n - Execute a basic handshake with SNI:
openssl s_client -connect host:port -servername host .\n - Observe the
Protocol,Cipher, andVerify return codelines. \n - If verification fails, add
-showcertsto inspect the chain and compare fingerprints. \n - If the handshake fails entirely, retry with forced TLS versions (
-tls1_2,-tls1_3) and/or different SNI values. \n - Scriptable checks: add
-verify_return_errorand test the exit code ($?) in your automation. \n
By following these steps you can quickly pinpoint whether a TLS problem lies in the certificate chain, protocol negotiation, SNI handling, or an intermediate network device, and you have a repeatable method to verify the fix.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.