# Check supported TLS versions and ciphers

To find out which TLS versions a server accepts, run `openssl s_client` once per version with the matching flag — `-tls1_3`, `-tls1_2`, `-tls1_1`, `-tls1` — and see which handshakes complete. A successful connection prints a `Protocol` and `Cipher` line; a refused version fails the handshake instead. The result is misleading on OpenSSL 3 unless you know that its default security level refuses TLS 1.0 and 1.1 on the client side, so those versions appear disabled on the server when it is your own OpenSSL declining them — add `-cipher DEFAULT@SECLEVEL=0` to test them honestly. Cipher selection is split by version: `-cipher` controls TLS 1.2 and below, while TLS 1.3 suites are selected with `-ciphersuites`, and using the wrong flag silently changes nothing.

Source: https://certguru.io/openssl/check-tls-versions-and-ciphers/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

## Reading what was negotiated

Every successful handshake reports the version and suite that were actually agreed. That is the
ground truth — everything else on this page is a way of constraining the negotiation to find the
edges:

*What did this connection settle on?*

```bash
openssl s_client -connect example.com:443 -servername example.com /dev/null \
  | grep -E "^ *(Protocol|Cipher) *:"
```

Expected output:

```
Protocol  : TLSv1.3
Cipher    : TLS_AES_256_GCM_SHA384
```

> **Always send SNI**
>
> Without `-servername`, a virtual-hosted server answers as its default host, which may have an
>   entirely different TLS policy. Every result below is meaningless without it.

## Probing each version

Ask for one version at a time. A version the server accepts completes the handshake; one it has
disabled fails:

*Which versions does the server accept?*

```bash
for v in tls1_3 tls1_2 tls1_1 tls1; do
  printf '%-8s ' "$v"
  openssl s_client -"$v" -connect example.com:443 -servername example.com &1 \
    | grep -qE "^ *Cipher *: *[A-Z0-9]" && echo "accepted" || echo "refused"
done
```

Expected output:

```
tls1_3   accepted
tls1_2   accepted
tls1_1   refused
tls1     refused
```

> **The bottom two rows may be your own client**
>
> OpenSSL 3 runs at security level 1 by default, which excludes the key exchanges and signature
>   algorithms TLS 1.0 and 1.1 depend on. Your client refuses to make the offer, so the server never
>   gets to answer, and the result is indistinguishable from a server that has disabled them.

To test those two honestly, lower your own client's policy for the duration of the probe:

*Test TLS 1.0 without your client vetoing it first*

```bash
openssl s_client -tls1 -cipher 'DEFAULT@SECLEVEL=0' \
  -connect example.com:443 -servername example.com &1 | head -20
```

A server still accepting TLS 1.0 in 2026 is a finding, not a configuration to preserve.

## Cipher suites, and the flag that catches everyone

TLS 1.3 replaced the suite namespace entirely, and OpenSSL kept them on separate switches:

| Version | Flag | Example value |
| --- | --- | --- |
| TLS 1.2 and below | `-cipher` | `ECDHE-RSA-AES256-GCM-SHA384` |
| TLS 1.3 | `-ciphersuites` | `TLS_AES_128_GCM_SHA256` |

Passing a TLS 1.3 suite to `-cipher` is accepted and does nothing, which reads as the server
ignoring your request. Start from what your own build offers:

*TLS 1.3 suites this OpenSSL supports*

```bash
openssl ciphers -s -tls1_3 -v
```

*Modern TLS 1.2 suites, expanded*

```bash
openssl ciphers -v 'ECDHE+AESGCM'
```

> **This says nothing about the server**
>
> `openssl ciphers` reports what your local build would be willing to offer. It is the input to a
>   negotiation, not the result of one.

Then test whether the server will accept a specific suite:

*Will the server take this one suite?*

```bash
openssl s_client -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' \
  -connect example.com:443 -servername example.com &1 \
  | grep -E "^ *Cipher *:"
```

`no ciphers available` or a `handshake failure` alert means the server declined that suite.

## Enumerating everything the server accepts

Walk your build's list and record which ones survive. This is slow — one handshake per suite — so
narrow the list before running it:

*Which TLS 1.2 suites does the server accept?*

```bash
for c in $(openssl ciphers -s -tls1_2 'ECDHE' | tr ':' ' '); do
  openssl s_client -tls1_2 -cipher "$c" \
    -connect example.com:443 -servername example.com &1 \
    | grep -qE "^ *Cipher *: *$c" && echo "accepted  $c"
done
```

## ALPN and OCSP stapling

Two other properties of the same handshake, worth checking while you are here. HTTP/2 requires
successful ALPN negotiation:

*Does the server negotiate HTTP/2?*

```bash
openssl s_client -alpn h2 -connect example.com:443 -servername example.com /dev/null \
  | grep -i "ALPN"
```

Expected output:

```
ALPN protocol: h2
```

And stapling, which saves every visitor an OCSP round trip:

*Is an OCSP response stapled?*

```bash
openssl s_client -status -connect example.com:443 -servername example.com /dev/null \
  | grep -A3 "OCSP response"
```

`no response sent by server` means stapling is off or the server's fetch from the CA is failing —
see [OCSP vs CRL](/concepts/ocsp-vs-crl/) for what that costs.

## Related checks

To fetch and decode the certificate itself rather than interrogate the connection, see
[get a certificate from a server](/openssl/get-certificate-from-server/). If the handshake never
starts and OpenSSL reports a wrong version number, the port is probably not speaking TLS at all —
that is [a different problem](/errors/wrong-version-number/).


## Frequently asked questions

### Why does TLS 1.0 fail even against a server I know still allows it?

OpenSSL 3 ships with security level 1, which refuses the signature and cipher combinations TLS 1.0 needs, so your client rejects the connection before the server has a say. Add `-cipher DEFAULT@SECLEVEL=0` to test it. Treat a server that requires this as one to replace.

### What is the difference between -cipher and -ciphersuites?

`-cipher` selects cipher suites for TLS 1.2 and below; `-ciphersuites` selects the separate TLS 1.3 suite list. They are independent, and passing a TLS 1.3 suite name to `-cipher` is accepted and has no effect — which looks like the server ignoring your request.

### Does openssl ciphers tell me what the server supports?

No. It lists what your local OpenSSL build supports and would be willing to offer. Only a completed handshake tells you anything about the server.

### How do I check whether a server staples OCSP?

Add `-status` to the s_client command. A stapling server returns an OCSP Response section; `no response sent by server` means stapling is off or failing.

### The handshake fails with "wrong version number" rather than a protocol alert. What is different?

That is not a version problem at all — it means the response was not a TLS record, which usually means the port is serving plaintext. See wrong version number.


## References

- [openssl-s_client(1)](https://docs.openssl.org/3.5/man1/openssl-s_client/) — the version, -cipher, -ciphersuites, -alpn and -status options
- [openssl-ciphers(1)](https://docs.openssl.org/3.5/man1/openssl-ciphers/) — cipher list syntax and what the security levels exclude
- [RFC 8446: TLS 1.3](https://www.rfc-editor.org/rfc/rfc8446) — why TLS 1.3 suite names are a separate namespace from earlier versions

## Related pages

- https://certguru.io/openssl/get-certificate-from-server/
- https://certguru.io/errors/wrong-version-number/
- https://certguru.io/openssl/verify-certificate-chain/
- https://certguru.io/platforms/nginx/
- https://certguru.io/concepts/mtls/
- https://certguru.io/concepts/ocsp-vs-crl/

--
CertGuru — https://certguru.io
