# openssl s_client: test TLS connections, SNI and STARTTLS

> Practical openssl s_client commands: SNI and -servername, -showcerts, TLS 1.2/1.3, ALPN, STARTTLS for mail and databases, stapling, mTLS.

Source: https://ssl.sh/cookbook/test-connections/
Last updated: 2026-11-10

`openssl s_client` is the universal TLS test client: it shows the chain, protocol version, cipher suite, key exchange group and verification result for any TLS endpoint, including STARTTLS mail and database servers. Outputs come from OpenSSL 3.6.1; options are the same in 3.0 to 3.6 unless stated. For a browser-based check use the [SSL Checker](https://ssl.sh/tools/ssl-checker/).

## Get a one-screen summary of a connection

`-brief` prints only protocol, cipher, certificate, verification and key exchange group.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com -brief < /dev/null
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=www.example.com
Hash used: SHA256
Signature type: ecdsa_secp256r1_sha256
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE
```

`Negotiated TLS1.3 group: X25519MLKEM768` means a hybrid post-quantum key exchange was used; OpenSSL 3.5 and later offer it by default. Against a server without it you see `Peer Temp Key: X25519, 253 bits` instead. Without `-brief` the full output adds the chain, session details and the session ticket.

## Send the right hostname with -servername (SNI)

Since OpenSSL 1.1.1, s_client sends SNI automatically when `-connect` uses a hostname; with an IP address you must pass `-servername`.

```bash
openssl s_client -connect 203.0.113.10:443 -servername www.example.com -brief < /dev/null
openssl s_client -connect 203.0.113.10:443 -noservername -brief < /dev/null
```

```text
Peer certificate: CN=www.example.com
Verification: OK
...
Peer certificate: CN=localhost
Verification error: self-signed certificate
```

The second command sends no SNI, so the server falls back to its default certificate. That is exactly what old clients without SNI see. On OpenSSL 1.1.0 and earlier, SNI was only sent with `-servername`, which is why many older tutorials always include it. See [SNI](https://ssl.sh/glossary/sni/).

## Test a specific origin IP while keeping the hostname

Connect to the IP, send the real name in SNI and in the HTTP `Host` header; useful behind a CDN or load balancer.

```bash
printf 'HEAD / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n' \
  | openssl s_client -quiet -connect 203.0.113.10:443 -servername www.example.com
```

```text
depth=2 C=US, O=Example CA, CN=Example Root R1
verify return:1
depth=1 C=US, O=Example CA, CN=Example TLS Issuing CA
verify return:1
depth=0 CN=www.example.com
verify return:1
HTTP/1.1 200 OK
```

`-quiet` hides the session dump and implies `-ign_eof`, so s_client waits until the server closes; `Connection: close` makes that happen right after the response. This is how you test a new origin server before switching DNS.

## Show the full chain the server sends

`-showcerts` prints every certificate the server sends, in order, as PEM.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts < /dev/null
```

```text
Certificate chain
 0 s:CN=www.example.com
   i:C=CN, O=Example Corp, CN=Example Intermediate CA
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Nov  9 18:16:48 2026 GMT; NotAfter: Dec 11 18:16:48 2027 GMT
-----BEGIN CERTIFICATE-----
MIICXzCCAgWgAwIBAgICEAAwCgYIKoZIzj0EAwIw...
-----END CERTIFICATE-----
 1 s:C=CN, O=Example Corp, CN=Example Intermediate CA
   i:C=CN, O=Example Corp, CN=Example Root CA
...
```

`s:` is the subject and `i:` the issuer; recent releases also print `a:` (key and signature algorithm) and `v:` (validity). Each `i:` should equal the next `s:`. A single entry means the intermediate is missing; see [verifying chains](https://ssl.sh/cookbook/verify-chains/).

## Force TLS 1.2 or TLS 1.3

`-tls1_2` and `-tls1_3` pin the protocol version, so you can confirm both are enabled.

```bash
openssl s_client -connect www.example.com:443 -tls1_2 -brief < /dev/null
openssl s_client -connect www.example.com:443 -tls1_3 -brief < /dev/null
```

```text
Protocol version: TLSv1.2
Ciphersuite: ECDHE-ECDSA-AES256-GCM-SHA384
...
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
```

TLS 1.2 cipher names use OpenSSL style (`ECDHE-ECDSA-AES256-GCM-SHA384`); TLS 1.3 uses IANA names. To test TLS 1.0 or 1.1 on OpenSSL 3.x you must also lower the security level, as shown in [ciphers and protocols](https://ssl.sh/cookbook/ciphers-and-protocols/).

## Check ALPN and HTTP/2 support

`-alpn` offers application protocols; the server's choice appears as `ALPN protocol`.

```bash
openssl s_client -connect www.example.com:443 -alpn h2,http/1.1 < /dev/null 2>/dev/null | grep -i alpn
```

```text
ALPN protocol: h2
```

`No ALPN negotiated` means the server ignored ALPN, so browsers will fall back to HTTP/1.1. HTTP/3 runs over QUIC, not TCP; s_client gained a `-quic` option in OpenSSL 3.2, but most people test HTTP/3 with `curl --http3`.

## Exit cleanly in scripts

Feed s_client an empty stdin so it exits after the handshake instead of waiting for input.

```bash
openssl s_client -connect www.example.com:443 < /dev/null
echo | openssl s_client -connect www.example.com:443
openssl s_client -connect www.example.com:443 -no-interactive   # OpenSSL 3.2+
```

```text
...
Verify return code: 0 (ok)
---
DONE
```

`DONE` means s_client closed the connection on its own. In a pipeline, redirect stderr (`2>/dev/null`) to drop the `depth=` lines. In interactive mode, a line starting with `Q` quits and `R` renegotiates (TLS 1.2 only), which can surprise you when you type an HTTP request; use `-ign_eof` or `-quiet` in that case.

## Test SMTP with STARTTLS

`-starttls smtp` sends EHLO and STARTTLS first, then starts TLS on the same connection.

```bash
openssl s_client -connect smtp.example.com:587 -starttls smtp -brief < /dev/null
openssl s_client -connect smtp.example.com:25 -starttls smtp -name mx-test.example.com < /dev/null
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=smtp.example.com
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE
```

`-name` sets the hostname used in EHLO (also used for LMTP and XMPP). Port 465 (SMTPS) uses implicit TLS, so connect without `-starttls`. Many residential and cloud networks block outbound port 25; test 587 or test from the mail server itself. The [STARTTLS Checker](https://ssl.sh/tools/starttls-checker/) runs this from outside your network.

## Test IMAP, POP3, FTP, databases and LDAP

The same `-starttls` option covers the other common protocols; implicit-TLS ports need no `-starttls`.

```bash
openssl s_client -connect mail.example.com:143  -starttls imap     < /dev/null
openssl s_client -connect mail.example.com:110  -starttls pop3     < /dev/null
openssl s_client -connect ftp.example.com:21    -starttls ftp      < /dev/null
openssl s_client -connect db.example.com:5432   -starttls postgres < /dev/null
openssl s_client -connect db.example.com:3306   -starttls mysql    < /dev/null
openssl s_client -connect ldap.example.com:389  -starttls ldap     < /dev/null
openssl s_client -connect mail.example.com:993  -brief             < /dev/null
```

```text
Didn't find STARTTLS in server response, trying anyway...
...:error:0A000126:SSL routines::unexpected eof while reading:...
```

The output above is what you get when the server does not advertise STARTTLS: s_client tries anyway and the server drops the connection. Supported keywords in OpenSSL 3.x are smtp, pop3, imap, ftp, xmpp, xmpp-server, irc, postgres, mysql, lmtp, nntp, sieve and ldap.

| Protocol | STARTTLS port | Implicit TLS port |
|---|---|---|
| SMTP submission | 587 | 465 |
| IMAP | 143 | 993 |
| POP3 | 110 | 995 |
| LDAP | 389 | 636 |

## Check OCSP stapling

`-status` asks the server to staple an OCSP response and prints it if one arrives.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com -status < /dev/null 2>/dev/null \
  | sed -n '/OCSP response/,/Next Update/p'
```

```text
OCSP responses: number of responses: 1
======================================
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    ...
    Cert Status: good
    This Update: Nov  9 04:50:45 2026 GMT
    Next Update: Nov 16 04:50:45 2026 GMT
```

`OCSP responses: no responses sent` is normal for certificates without an OCSP URL, which includes all Let's Encrypt certificates since 2025. Older OpenSSL versions print `OCSP response: no response sent`. More in [OCSP and CRL commands](https://ssl.sh/cookbook/ocsp-crl/).

## Present a client certificate (mutual TLS)

Pass `-cert` and `-key` (and `-cert_chain` for intermediates) when the server asks for a client certificate.

```bash
openssl s_client -connect api.example.com:443 -servername api.example.com \
  -cert client.crt -key client.key -cert_chain intermediate.pem -brief < /dev/null
```

```text
Acceptable client certificate CA names
C=CN, O=Example Corp, CN=Example Intermediate CA
C=CN, O=Example Corp, CN=Example Root CA
...
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE
```

The `Acceptable client certificate CA names` list (shown in full output) tells you which CAs the server accepts. Without a certificate, a TLS 1.3 server fails after the handshake with `tlsv13 alert certificate required ... SSL alert number 116`; a certificate from the wrong CA gives `tlsv1 alert unknown ca ... SSL alert number 48`. See [mTLS](https://ssl.sh/learn/mtls/).

## Test session resumption

Save a session with `-sess_out`, reconnect with `-sess_in`, and look for `Reused`.

```bash
sleep 1 | openssl s_client -connect www.example.com:443 -sess_out sess.pem 2>/dev/null | grep -E '^(New|Reused)'
sleep 1 | openssl s_client -connect www.example.com:443 -sess_in sess.pem 2>/dev/null | grep -E '^(New|Reused)'
```

```text
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
```

TLS 1.3 tickets arrive after the handshake, so `sleep 1` keeps the connection open long enough to receive them. `-reconnect` (five reconnections with the same session) works well for TLS 1.2 but usually reports `New` every time on TLS 1.3 for the same reason. See [session resumption](https://ssl.sh/glossary/session-resumption/).

## Connect through an HTTP proxy

`-proxy host:port` tunnels through an HTTP CONNECT proxy, as in many corporate networks.

```bash
openssl s_client -proxy proxy.example.com:3128 -connect www.example.com:443 \
  -servername www.example.com -brief < /dev/null
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=www.example.com
```

Use `-proxy_user` and `-proxy_pass` for authenticated proxies. If the peer certificate comes from a corporate CA you do not recognise, the proxy is intercepting TLS; that explains [unknown-authority errors](https://ssl.sh/errors/x509-certificate-signed-by-unknown-authority/) inside such networks.

## Debug the handshake message by message

`-msg` lists each handshake message, `-trace` decodes them, and `-keylogfile` writes secrets Wireshark can use.

```bash
openssl s_client -connect www.example.com:443 -msg < /dev/null 2>&1 | grep -E '^(>>>|<<<)'
openssl s_client -connect www.example.com:443 -keylogfile keys.log < /dev/null
```

```text
>>> TLS 1.3, Handshake [length 0608], ClientHello
<<< TLS 1.3, Handshake [length 04ba], ServerHello
<<< TLS 1.3, Handshake [length 000a], EncryptedExtensions
<<< TLS 1.3, Handshake [length 0d7d], Certificate
<<< TLS 1.3, Handshake [length 004f], CertificateVerify
<<< TLS 1.3, Handshake [length 0034], Finished
```

The ClientHello here is 1,544 bytes (0x608) because it carries a 1,216-byte X25519MLKEM768 key share. `-trace` needs OpenSSL built with trace support (Homebrew's build has it) and shows every extension, including `key_share` and `server_name`. Treat `keys.log` like a private key and delete it afterwards. The message flow is explained in [the TLS handshake](https://ssl.sh/learn/tls-handshake/).
