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
- OpenSSL 1.1.1 or later. Earlier branches have no TLS 1.3 code, so the flag fails locally and never reaches the network. Check with
openssl version. - curl 7.61 or later for step 3, linked against OpenSSL 1.1.1, LibreSSL 3.4 or a later equivalent.
curl --versionnames the library. - The s_client manual for the meaning of each flag.
Steps
- 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/nullmatters. Without it, s_client waits on standard input and the command never returns. - 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 40Alert 40 is
handshake_failure. The stringsslv3in 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 ishandshake_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. - 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.3sets the floor at 1.3 and--tls-max 1.3sets 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. - 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
Thresholds
What to check next
- How to check TLS version of a website: what a normal connection negotiated, as opposed to what the server can do.
- How to check cipher suites of a server: TLS 1.3 has three suites, and which one is chosen is a separate setting.
- How to check the TLS handshake: the message exchange behind the alert numbers above.
- How to test SSL configuration: the full version matrix in one loop.
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.
Related on this site
basic5 minpublished updated Maks Verny