Verification and testing

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

By · Last verified Tested macOS · OpenSSL 3.6.1

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; to look up a single suite use the 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.

openssl ciphers -v 'ECDHE+AESGCM'
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.

openssl ciphers -s -tls1_3 -v
openssl ciphers -s -tls1_2 | tr ':' '\n' | wc -l
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.

openssl ciphers -stdname -s -tls1_2 'ECDHE+CHACHA20'
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.

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
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.

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
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.

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
...: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.

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
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.

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'
   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.

openssl s_client -connect www.example.com:443 -groups X25519MLKEM768 -brief < /dev/null
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 and X25519MLKEM768.

See the groups offered and the key share sizes

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

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
        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.

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'
  { 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.

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
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.

FAQ

Why does openssl s_client -tls1_1 fail with no protocols available?

That error is local. OpenSSL 3.x disables TLS 1.0 and 1.1 at the default security level 1 because they depend on SHA-1/MD5 signatures. Add -cipher 'DEFAULT:@SECLEVEL=0' to test them. If the server then answers with alert protocol version (alert 70), it really has TLS 1.1 disabled.

Is there a TLS 1.3 equivalent of ECDHE-RSA-AES128-GCM-SHA256?

TLS 1.3 suites only name the AEAD cipher and hash, for example TLS_AES_128_GCM_SHA256. Key exchange is negotiated separately through supported groups (X25519, X25519MLKEM768, P-256) and authentication through signature algorithms, so RSA versus ECDSA no longer appears in the suite name.

Can I enumerate ciphers without installing extra scanners?

Yes. The enumeration loops above try one suite per connection with openssl s_client and print the ones the server accepts. They are slower than dedicated scanners but need nothing beyond OpenSSL.

Sources

  1. OpenSSL manual: openssl-ciphers
  2. OpenSSL manual: openssl-list
  3. OpenSSL 3.0 migration guide (TLS 1.0/1.1 need security level 0)
  4. OpenSSL manual: SSL_CTX_set_security_level
  5. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3