> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# openssl s_client Answers Every TLS Question
- URL: https://debugly.dev/openssl-s-client-tls-debugging/
- Published: 2026-09-19T07:30:00.000Z
- Updated: 2026-10-17T05:57:43.000Z
- Description: Chain broken, SNI wrong, protocol refused, certificate expiring. The TLS questions that produce guesswork are all answerable from one command, and the…
- Author: Rohit Bhadani
- Tags: Tooling, Security, Networking

Half of TLS debugging is done by changing something and seeing if the error changes, which is guessing with extra steps. The other half can be done by asking the server directly what it presents and what it accepts, and `openssl s_client` is the tool that asks. It performs a real handshake from your machine and prints everything the server said, which converts most TLS mysteries into reading.

This was OpenSSL 3.4\. The flags below are stable across recent versions.

## The one invocation

```bash
openssl s_client -connect host:443 -servername host

```

The `-servername` is not optional in practice. It sends the SNI extension, which is how a server hosting many certificates on one address chooses which to present. Omitting it gets you the default certificate, which is frequently the wrong one, and the resulting confusion is itself a common misdiagnosis. If a site works in browsers but fails in your client, the first question is whether your client sends SNI and what the server returns for it.

## Reading the five sections

The output is long but structured, and five sections answer almost everything.

**The certificate chain.** Printed under "Certificate chain", each level numbered. You want to see your leaf at zero, intermediates above, and the chain complete to a root your client trusts. A missing intermediate is the most common chain defect: browsers paper over it with cached intermediates, and strict clients fail with unable to verify. The verify code at the bottom, `Verify return code: 0 (ok)` or a number and a reason, is the headline.

**The subject and dates.** Under the leaf certificate, the subject names the domain, and the notBefore and notAfter dates bound its validity. An expired certificate is a verify error whose cause is two lines down, and a subject that does not match the name you connected with is the SNI or hostname question answered.

**The negotiated protocol and cipher.** Near the end, "Protocol" and "Cipher" say what was actually agreed, as opposed to what either side claims to support. This settles "does it really do TLS 1.3" with a fact.

**The SAN entries.** The subject alternative names list the domains the certificate covers. A certificate that verifies for one hostname and fails for another is usually a SAN that lacks the second name, and reading the list is faster than arguing about it.

**The session and resumption block.** At the end, whether a session ticket was issued and whether a reused session would be possible, which is the client side view of [TLS session resumption](https://debugly.dev/tls-session-resumption-and-why-it-fails/).

## The targeted queries

Beyond the default handshake, a few flags answer specific questions.

**Which protocols are accepted.** Force one and see if the handshake completes:

```bash
openssl s_client -connect host:443 -servername host -tls1_2
openssl s_client -connect host:443 -servername host -tls1_3

```

A refusal on one and success on the other tells you the server's protocol floor and ceiling, which is the answer when an old client fails and a new one works.

**The full chain as the server sends it.** Add `-showcerts` to print every certificate in the chain, so you can count the intermediates yourself and see the one that is missing.

**What a specific SNI gets.** Change `-servername` to each hostname you serve and compare the subjects returned. A default certificate served for a name that should have its own is the multi tenant TLS misconfiguration, found in seconds.

**The certificate alone, for expiry automation.** Pipe to extract the end date and compare:

```bash
echo | openssl s_client -connect host:443 -servername host 2>/dev/null \
  | openssl x509 -noout -enddate

```

That one liner is the basis of every honest certificate expiry alert, as opposed to alerting on the date you remember issuing.

## The traps

**The handshake succeeding is not the application working.** s\_client proves the TLS layer. A 502 beyond it is a different story, the territory of [nginx 502 bad gateway](https://debugly.dev/nginx-502-bad-gateway-debugging/). Do not let a good handshake close an investigation whose error lives above it.

**Your machine is not your user's machine.** s\_client uses your trust store and your network path. A corporate proxy that intercepts TLS shows you its certificate, not the origin's, and a user on a different path may see a different chain. When the report disagrees with your command, ask which chain the reporter's machine sees.

**The tool shows what the server presents now.** If certificates rotate, the answer changes with time, and an intermittent verify failure can be a load balancer serving two different chains from two nodes, one updated and one not. Repeating the command several times and comparing subjects catches that, because the disagreement between runs is the diagnosis.

## The rule I keep

TLS debugging without s\_client is inference, and with it is reading. Send the SNI, read the chain, the dates, the SAN and the negotiated protocol, and use the forced protocol flags to map what the server accepts. The server already knows the answer to every TLS question you are guessing at, and this is the command that makes it say so.

For the layer below the handshake, where the question is whether the packets arrive at all, the next tool is [tcpdump](https://debugly.dev/tcpdump-for-web-developers/), and for the layer above, where the handshake is fine but the proxy fails, the 502 and 504 posts in this series cover the partition.

## A worked five minute session

To make the reading concrete, a typical session goes like this. The report says the mobile app fails TLS while browsers work. Run s\_client with the app's exact hostname as SNI and the verify line says unable to get local issuer certificate, which is the missing intermediate, and the chain section shows only the leaf, confirming the server sends an incomplete chain that browsers repair from cache and strict clients do not. Add the intermediate to the server's chain file, rerun, and the chain now shows level one and verify returns zero. The whole incident was two commands and a chain file, and every minute of guessing it replaced was a minute spent theorising about the client. That ratio is the argument for the tool.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)