# OpenSSL cipher suites and TLS versions: list, test, enumerate

> OpenSSL 3.x commands to expand cipher strings, test TLS 1.0-1.3, enumerate a server's cipher suites and check X25519MLKEM768 support.

Source: https://ssl.sh/cookbook/ciphers-and-protocols/
Last updated: 2026-11-10

`openssl ciphers` shows what a cipher string means on your build, and `openssl s_client` shows which protocol versions, cipher suites and key exchange groups a server accepts. Output is from OpenSSL 3.6.1; commands that need 3.4 or 3.5 are marked. For the concepts see [cipher suites explained](https://ssl.sh/learn/cipher-suites/); to look up a single suite use the [Cipher Suite Lookup](https://ssl.sh/tools/cipher-suite-lookup/).

## Expand a cipher string into suites

`openssl ciphers -v` shows exactly which suites a string such as `ECDHE+AESGCM` selects, with key exchange, authentication and encryption.

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

```text
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256)            Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(256)            Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128)            Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(128)            Mac=AEAD
```

The three TLS 1.3 suites are always listed because the cipher string does not control them. `+` means "both conditions", `:` separates alternatives, `!` removes permanently and `@STRENGTH` sorts. The TLS 1.2 column is the minimum version a suite can be used with, not the only one.

## Show what one protocol version would use

`-s` lists only suites usable with the current settings; combine it with `-tls1_2` or `-tls1_3`.

```bash
openssl ciphers -s -tls1_3 -v
openssl ciphers -s -tls1_2 | tr ':' '\n' | wc -l
```

```text
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD
      27
```

OpenSSL enables three of the five TLS 1.3 suites by default; `TLS_AES_128_CCM_SHA256` and `TLS_AES_128_CCM_8_SHA256` must be switched on explicitly. The second command counts the default suites usable with TLS 1.2 on this build (27; the three TLS 1.3 suites come on top). Distributions often patch the default, so check yours.

## Translate OpenSSL names to IANA names

`-stdname` prints the RFC/IANA name next to the OpenSSL name, which is what browsers, Java and Go use.

```bash
openssl ciphers -stdname -s -tls1_2 'ECDHE+CHACHA20'
```

```text
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - ECDHE-ECDSA-CHACHA20-POLY1305  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256   - ECDHE-RSA-CHACHA20-POLY1305    TLSv1.2 Kx=ECDH     Au=RSA   Enc=CHACHA20/POLY1305(256) Mac=AEAD
```

Nginx, Apache and HAProxy take OpenSSL names in `ssl_ciphers`; Java and Go configuration use IANA names. Mixing them up is a common reason a cipher list is silently ignored.

## Pin TLS 1.3 suites with -ciphersuites, not -cipher

`-ciphersuites` controls TLS 1.3 suites; `-cipher` has no effect on a TLS 1.3 handshake.

```bash
openssl s_client -connect www.example.com:443 -tls1_3 -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -brief < /dev/null
openssl s_client -connect www.example.com:443 -tls1_3 -cipher ECDHE-RSA-AES128-GCM-SHA256 -brief < /dev/null
```

```text
Protocol version: TLSv1.3
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256
...
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
```

The second connection ignored `-cipher` and negotiated the default. The same split exists in server configuration: Nginx's `ssl_ciphers` only covers TLS 1.2 and below, and TLS 1.3 suites are set through `ssl_conf_command Ciphersuites ...` (Nginx 1.19.4 and later). The defaults are fine for almost everyone.

## Check which TLS versions a server accepts

Loop over the version flags and print the result of each handshake.

```bash
for v in tls1_2 tls1_3; do
  printf '%s: ' "$v"
  openssl s_client -connect www.example.com:443 -servername www.example.com -"$v" -brief < /dev/null 2>&1 \
    | grep -m1 -E 'Protocol version|alert|error' || echo failed
done
```

```text
tls1_2: Protocol version: TLSv1.2
tls1_3: Protocol version: TLSv1.3
```

A server that supports only TLS 1.2 answers the `-tls1_3` attempt with `alert protocol version` (alert 70). Disabling TLS 1.2 entirely is still premature for public sites as of November 2026, because some older clients and libraries lack TLS 1.3.

## Confirm that TLS 1.0 and 1.1 are really disabled

OpenSSL 3.x will not even offer TLS 1.0 or 1.1 at security level 1, so add `@SECLEVEL=0` before concluding anything.

```bash
openssl s_client -connect www.example.com:443 -tls1_1 < /dev/null
openssl s_client -connect www.example.com:443 -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0' < /dev/null
```

```text
...:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:...
...:error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:...:SSL alert number 70
```

The first error comes from your own client, not the server. The second is the server refusing TLS 1.1, which is what you want. If the second command connects instead, the server still allows TLS 1.1. The OpenSSL 3.0 migration guide documents this change; RFC 8996 deprecated TLS 1.0 and 1.1 in 2021.

## Enumerate every cipher suite a server accepts

Try each suite in its own handshake and keep the ones that succeed.

```bash
HOST=www.example.com; PORT=443
for c in $(openssl ciphers -tls1_2 'ALL:@SECLEVEL=0' | tr ':' ' '); do
  echo | openssl s_client -connect $HOST:$PORT -servername $HOST -tls1_2 \
    -cipher "$c:@SECLEVEL=0" >/dev/null 2>&1 && echo "TLSv1.2 $c"
done
for s in TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 \
         TLS_AES_128_CCM_SHA256 TLS_AES_128_CCM_8_SHA256; do
  echo | openssl s_client -connect $HOST:$PORT -servername $HOST -tls1_3 \
    -ciphersuites $s >/dev/null 2>&1 && echo "TLSv1.3 $s"
done
```

```text
TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
TLSv1.2 ECDHE-ECDSA-CHACHA20-POLY1305
TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256
TLSv1.2 ECDHE-ECDSA-AES256-SHA384
TLSv1.2 ECDHE-ECDSA-AES128-SHA256
TLSv1.2 ECDHE-ECDSA-AES256-SHA
TLSv1.2 ECDHE-ECDSA-AES128-SHA
TLSv1.3 TLS_AES_128_GCM_SHA256
TLSv1.3 TLS_AES_256_GCM_SHA384
TLSv1.3 TLS_CHACHA20_POLY1305_SHA256
```

This server (running OpenSSL defaults with an ECDSA certificate) still accepts four CBC suites for TLS 1.2. Only suites your local OpenSSL knows can be tested; suites removed from your build will never appear. Add `-tls1` or `-tls1_1` loops the same way if needed. `ALL:@SECLEVEL=0` expands to about 140 suites on OpenSSL 3.6, so expect that many connections; run it against your own servers, not in a tight loop.

## Check whether a server has both ECDSA and RSA certificates

Restrict signature algorithms with `-sigalgs`; a dual-certificate server returns the matching certificate.

```bash
openssl s_client -connect www.example.com:443 -sigalgs 'ECDSA+SHA256' < /dev/null 2>/dev/null | grep -m1 'a:PKEY'
openssl s_client -connect www.example.com:443 -sigalgs 'RSA-PSS+SHA256:RSA+SHA256' < /dev/null 2>/dev/null | grep -m1 'a:PKEY'
```

```text
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
```

If the second command fails with `alert handshake failure`, the server only has an ECDSA certificate, which shuts out a small number of very old clients. Serving both is easy in Nginx: repeat `ssl_certificate` and `ssl_certificate_key` once per key type.

## Test hybrid post-quantum key exchange (OpenSSL 3.5+)

`-groups X25519MLKEM768` offers only the hybrid ML-KEM group, so the handshake succeeds only if the server supports it.

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

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

A server without ML-KEM support answers `ssl/tls alert handshake failure ... SSL alert number 40`. With default settings OpenSSL 3.5+ sends key shares for both X25519MLKEM768 and X25519, so it falls back silently and shows `Peer Temp Key: X25519, 253 bits`. OpenSSL 3.0 to 3.4 reject the group name altogether. Background: [post-quantum TLS](https://ssl.sh/learn/post-quantum-tls/) and [X25519MLKEM768](https://ssl.sh/glossary/x25519mlkem768/).

## See the groups offered and the key share sizes

`-trace` decodes the ClientHello and ServerHello, including `supported_groups` and `key_share`.

```bash
openssl s_client -connect www.example.com:443 -trace < /dev/null 2>&1 \
  | grep -E 'supported_groups|key_share|NamedGroup|key_exchange:  \(len' | cut -c1-70
```

```text
        extension_type=supported_groups(10), length=18
        extension_type=key_share(51), length=1258
            NamedGroup: X25519MLKEM768 (4588)
            key_exchange:  (len=1216): 00DDC3A82B7EB93BC261440DE87C418A717D3
            NamedGroup: ecdh_x25519 (29)
            key_exchange:  (len=32): 233767194B5DECAE196362B3FC539DC6541371E
        extension_type=key_share(51), length=1124
            NamedGroup: X25519MLKEM768 (4588)
            key_exchange:  (len=1120): F4AB5DAE2E917CEBEA4E2B3BB80085B06DFE1
```

The client's hybrid share is 1,216 bytes (a 1,184-byte ML-KEM-768 encapsulation key plus 32 bytes of X25519) and the server's is 1,120 bytes (1,088-byte ciphertext plus 32 bytes). The group code point is 4588 (0x11EC). Large ClientHellos are the reason some old middleboxes break post-quantum handshakes.

## List KEM, signature and TLS group algorithms

`openssl list` shows which algorithms your build and its providers support.

```bash
openssl list -kem-algorithms
openssl list -tls-groups                  # OpenSSL 3.5+
openssl list -tls-signature-algorithms    # OpenSSL 3.4+
openssl list -signature-algorithms | grep -i -E 'ml-dsa|slh-dsa'
```

```text
  { 2.16.840.1.101.3.4.4.2, id-alg-ml-kem-768, ML-KEM-768, MLKEM768 } @ default
  X25519MLKEM768 @ default
  SecP256r1MLKEM768 @ default
  ...
secp256r1:secp384r1:secp521r1:x25519:x448:...:MLKEM512:MLKEM768:MLKEM1024:SecP256r1MLKEM768:X25519MLKEM768:SecP384r1MLKEM1024
```

ML-KEM (FIPS 203), ML-DSA (FIPS 204) and SLH-DSA (FIPS 205) arrived in the default provider in OpenSSL 3.5. On 3.0 to 3.4 none of them appear unless you load an external provider. `@ default` names the provider that implements the algorithm.

## Raise or lower the security level

`@SECLEVEL=n` in a cipher string changes which keys, signatures and protocol versions are acceptable.

```bash
openssl s_client -connect www.example.com:443 -cipher 'DEFAULT:@SECLEVEL=2' -brief < /dev/null
openssl ciphers -s -tls1 -v 'DEFAULT:@SECLEVEL=0' | head -3
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
...
ECDHE-ECDSA-AES256-SHA         TLSv1   Kx=ECDH     Au=ECDSA Enc=AES(256)               Mac=SHA1
ECDHE-RSA-AES256-SHA           TLSv1   Kx=ECDH     Au=RSA   Enc=AES(256)               Mac=SHA1
DHE-RSA-AES256-SHA             SSLv3   Kx=DH       Au=RSA   Enc=AES(256)               Mac=SHA1
```

Level 1 (the OpenSSL default) needs at least 80-bit security and forbids SHA-1 and MD5 signatures; level 2 requires RSA, DSA and DH keys of at least 2048 bits and ECC keys of at least 224 bits. Some distributions raise the system default to level 2, which is why a server with a 1024-bit RSA key or DH parameters fails with `dh key too small` on those systems. Use level 0 for testing legacy systems only.
