How to check if a website supports TLS 1.3

Force the version and read the result: openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null 2>&1 | grep -E 'New,|alert'. A line reading New, TLSv1.3 means the server accepts TLS 1.3. An SSL alert number 40 or 70 line means the server refused the offer.

Why check this

Support is a capability, not an observation. A connection that negotiated TLS 1.2 proves nothing about 1.3, because the client may never have offered it. Testers hit this when a compliance item says "TLS 1.3 enabled" and the evidence attached to the ticket is a screenshot of a browser that negotiated 1.2 through a corporate proxy. Run this check on staging sign-off and again after the certificate or the CDN configuration changes, since a version list is usually part of the same profile as the certificate.

Forcing the version removes the client from the decision. The server either answers in 1.3 or sends an alert.

There is a second reason to force it. TLS 1.3 announces itself in the supported_versions extension of the ClientHello rather than in the record version field. An inspecting proxy that understands only TLS 1.2 can drop that extension on the way out, and the connection then negotiates 1.2 with no error anywhere. Running the forced test from inside and from outside the network tells you whether the server lacks 1.3 or a device on the path removed the offer.

Prerequisites

Steps

  1. Step 1.

    Offer TLS 1.3 and only TLS 1.3, then read the negotiated line.

    openssl s_client -connect www.cloudflare.com:443 -servername www.cloudflare.com -tls1_3 </dev/null 2>&1 | grep '^New,'
    
    New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

    </dev/null matters. Without it, s_client waits on standard input and the command never returns.

  2. Step 2.

    Run the same command against a server that stops at TLS 1.2, so you know what a refusal looks like.

    openssl s_client -connect tls-v1-2.badssl.com:1012 -servername tls-v1-2.badssl.com -tls1_3 </dev/null 2>&1 | grep 'alert'
    
    6C4C0000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../openssl-3.1.1/ssl/record/rec_layer_s3.c:1586:SSL alert number 40

    Alert 40 is handshake_failure. The string sslv3 in the label is OpenSSL's name for the alert record format, not the protocol in use.

    Two alert numbers can come back from this test and they mean different things. Alert 70 is protocol_version, a direct statement that the version is out of range. Alert 40 is handshake_failure, which a server also sends when it cannot find a suitable key share or signature algorithm. Both count as a refusal here, and only alert 70 tells you the version itself was the reason.

  3. Step 3.

    Confirm with a second client, because one library's flag handling is not evidence on its own.

    curl -sI https://www.cloudflare.com/ -o /dev/null --tlsv1.3 --tls-max 1.3 -w 'http=%{http_code}\n'
    
    http=200

    --tlsv1.3 sets the floor at 1.3 and --tls-max 1.3 sets the ceiling, so exactly one version is offered and a 200 can only come from a 1.3 handshake. The same pair of flags against the TLS 1.2 host from step 2 exits with code 35, which is curl's generic TLS connect error and carries no alert number. That is why step 2 uses OpenSSL.

  4. Step 4.

    Compare support with what an unforced connection actually picked.

    curl -sI https://www.cloudflare.com/ -o /dev/null -v 2>&1 | grep 'SSL connection'
    
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / [blank] / UNDEF

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | New, TLSv1.3 and exit 0 | The server accepts TLS 1.3 | Record the cipher too. It tells you which of the three suites the server prefers. | | SSL alert number 40 | The server rejected the offer | TLS 1.3 is off, or a middlebox stripped the supported_versions extension. | | SSL alert number 70 | Version refused outright | The server's maximum version is below 1.3. | | no protocols available | Your OpenSSL has no TLS 1.3 | Nothing reached the network. Upgrade to 1.1.1 or later and repeat. | | Connection hangs with no output | s_client is waiting on stdin | Add </dev/null and run it again. |

Common mistakes

Sign: A report claims TLS 1.3 is off because the browser negotiated TLS 1.2.Cause: Negotiation reflects the weaker of the two sides. A client capped at 1.2, by an old runtime or an inspecting proxy, produces a 1.2 connection against a server that fully supports 1.3. Only a forced offer tests the server.
Sign: The command returns instantly with no protocols available and the server is blamed.Cause: That error comes from your own OpenSSL before any packet is sent. It means the local build cannot do the version you asked for. A server refusal always arrives as an alert number instead.
Sign: TLS 1.3 works from your laptop and fails from a build agent.Cause: The agent's container image ships an older OpenSSL or routes through an inspecting proxy that terminates at 1.2. Run openssl version on the agent before filing the difference as a server defect.

Thresholds

TLS 1.3 is the current version and TLS 1.2 is the lowest still permitted Source: RFC 8446 defines TLS 1.3; RFC 8996 deprecates TLS 1.0 and 1.1

What to check next

FAQ

How to test TLS 1.3 without installing anything?

Chrome shows the negotiated version in DevTools, Security tab, Connection section. That tests the browser's connection, not the server's capability, so a 1.2 result there needs the forced check above before it becomes a finding.

Does a TLS 1.3 result mean TLS 1.2 is off?

No. Almost every server keeps 1.2 enabled for older clients. Support for 1.3 and removal of 1.2 are independent settings, and turning 1.2 off breaks a measurable share of traffic.

Which cipher suites can TLS 1.3 use?

Three are defined for general use: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 and TLS_AES_128_GCM_SHA256. Key exchange and authentication moved out of the suite name in TLS 1.3, so the name only describes the record cipher.

Why does the error mention sslv3 when SSL 3.0 is long gone?

OpenSSL labels its alert handling code ssl3_read_bytes because the alert record layout has not changed since SSL 3.0. The label names the code path, not the protocol that was negotiated.

Verified

Verified by Maks Vernyopenssl 3.1.1curl 8.21.0

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny