How to check if TLS 1.0 is disabled

Run openssl s_client -connect example.com:443 -servername example.com -tls1 -cipher 'DEFAULT@SECLEVEL=0' </dev/null 2>&1 | grep error. A server with TLS 1.0 off answers tlsv1 alert protocol version and SSL alert number 70. Any other error means your own client refused before sending anything.

Why check this

A negative test passes for the wrong reason more often than any other check on this site. The command fails, the tester records "TLS 1.0 refused", and the failure came from the local OpenSSL security level rather than from the server. The audit item stays green while port 443 keeps answering TLS 1.0 handshakes. Run this on the release candidate and after every change to a TLS termination point, and run the control in step 2 every time so the result carries evidence that the client was capable.

Prerequisites

Steps

  1. Step 1.

    Offer TLS 1.0 to the server under test with the local security level lowered.

    openssl s_client -connect www.cloudflare.com:443 -servername www.cloudflare.com -tls1 -cipher 'DEFAULT@SECLEVEL=0' </dev/null 2>&1 | grep error
    
    680F0000:error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:../openssl-3.1.1/ssl/record/rec_layer_s3.c:1586:SSL alert number 70

    ssl3_read_bytes means the bytes came back from the network. The server sent the alert.

  2. Step 2.

    Run the identical command against a host known to accept TLS 1.0. This is the control that proves your client was able to make the offer.

    openssl s_client -connect tls-v1-0.badssl.com:1010 -servername tls-v1-0.badssl.com -tls1 -cipher 'DEFAULT@SECLEVEL=0' </dev/null 2>&1 | grep '^New,'
    
    New, TLSv1.0, Cipher is ECDHE-RSA-AES256-SHA

    Without this line, step 1 is not evidence of anything.

  3. Step 3.

    Drop the cipher flag and watch the same command fail locally, so you can recognise the false pass.

    openssl s_client -connect www.cloudflare.com:443 -servername www.cloudflare.com -tls1 </dev/null 2>&1 | grep error
    
    949D0000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:../openssl-3.1.1/ssl/statem/statem_lib.c:104:

    tls_setup_handshake runs before the ClientHello goes out. Step 1 read 7 bytes back from the server, the alert record. This run reads 0.

  4. Step 4.

    Confirm that curl cannot answer this question on a modern build, so nobody repeats the test with it.

    curl -I https://tls-v1-0.badssl.com:1010/ -o /dev/null --tlsv1.0 --tls-max 1.0
    
    curl: (35) TLS connect error: error:1404E0BF:SSL routines:ST_BEFORE_CONNECT:no protocols available

    The host in this command accepts TLS 1.0, and curl still reports a failure. The same message comes back from a server that refuses it.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | alert protocol version with SSL alert number 70 | The server refused TLS 1.0 on the wire | Pass. Attach the alert line and the step 2 control to the ticket. | | New, TLSv1.0, Cipher is ... | The server completed a TLS 1.0 handshake | Fail. TLS 1.0 is live on this listener. | | no protocols available | Your OpenSSL refused before connecting | Not a result. Add -cipher 'DEFAULT@SECLEVEL=0' and run it again. | | SSL alert number 40 | Version accepted, no shared cipher | The version may still be enabled. Widen the cipher list before concluding. | | curl: (35) on any build | curl declined to speak TLS 1.0 | Ignore. Use OpenSSL for this check. |

Common mistakes

Sign: The command fails, the item is marked as passed, and a later audit finds TLS 1.0 open.Cause: OpenSSL 3 refuses to offer TLS 1.0 at its default security level, so the failure is local. The give-away is the absence of an alert number and the words tls_setup_handshake in the error.
Sign: Alert 40 appears and the report records TLS 1.0 as disabled.Cause: Alert 40 is handshake_failure, which means the version was acceptable and no cipher was. A server that answers 40 to TLS 1.0 and completes with a wider cipher list still has the version enabled.
Sign: Port 443 refuses TLS 1.0 and an internal client still connects over it.Cause: The check covered one listener. Admin ports, mail submission, legacy API ports and a second load balancer each carry their own TLS profile. Repeat the command per host and port.

Thresholds

TLS 1.0 and TLS 1.1 must not be used Source: RFC 8996, Deprecating TLS 1.0 and TLS 1.1, March 2021

What to check next

FAQ

Does a PCI or ASV scan report replace this check?

It covers the hosts the scanner was pointed at. Internal listeners, staging load balancers and non-standard ports are usually outside its scope, and those are where an old TLS profile survives.

How do I test TLS 1.1 the same way?

Replace -tls1 with -tls1_1. The same @SECLEVEL=0 suffix is needed, and the same alert 70 is the expected refusal.

Can I run this from Windows without OpenSSL?

PowerShell can open a SslStream with an explicit protocol, which gives the same signal. Schannel builds of curl behave like the LibreSSL build in step 4 and cannot offer TLS 1.0 either.

Why does the server send alert 70 rather than closing the connection?

RFC 5246 defines protocol_version as the alert for a version the server will not accept. A silent close is also legal, so treat a connection reset with zero bytes read as a refusal that needs a second opinion.

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.

intermediate6 minpublished updated Maks Verny