Skip to main content
DevOps Insightsguides & answers

TLS · troubleshooting

SSL routines::wrong version number

Your client started a TLS (https) handshake, and the server answered in plain text. Almost always https:// on a port that speaks plain HTTP, implicit TLS on a mail port that expects STARTTLS, or a proxy URL written https:// for a plain proxy.

Reproduced on OpenSSL 3.0.13, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 9 sections

Before you start

  • The exact URL, host and port, and any proxy settings (HTTPS_PROXY, https_proxy) in the environment.

Run this first

step 1 of 2
openssl s_client -connect HOST:PORT -servername HOST </dev/null

Look for wrong version number (plain text answered) or a certificate chain (TLS works there)

Attempts only the TLS handshake against that port, outside your application.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

Does env | grep -i '_proxy=' show a proxy URL starting https://?

Cause space

3 of 3 still possible

  • https:// to a port that serves plain HTTPCommon
  • The proxy URL says https:// but the proxy speaks HTTPCommon
  • Implicit TLS on a mail port that expects STARTTLSOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    openssl s_client -connect HOST:PORT -servername HOST </dev/null

    Attempts only the TLS handshake against that port, outside your application.

    Look for wrong version number (plain text answered) or a certificate chain (TLS works there)

  2. Step 2

    env | grep -i '_proxy='

    Shows whether a proxy is in the path, and how its URL is written.

    Look for A proxy URL starting https://

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

https:// to a port that serves plain HTTP

Common

An app or dev server listening without TLS (often 80, 3000, 8080), addressed with https://, or a load balancer listener configured for HTTP on the port you use.

Confirm

curl -sS -o /dev/null -w '%{http_code}\n' http://HOST:PORT/

Any HTTP status: the port speaks plain HTTP

Fix

  • Use http:// for that port if the connection stays on a trusted network, or connect to the port that really serves TLS (usually 443).
  • If TLS was meant to be there, check the server or load balancer listener on that port.

The proxy URL says https:// but the proxy speaks HTTP

Common

Most forward proxies take plain HTTP from the client and open a tunnel for https sites. Writing HTTPS_PROXY=https://… tells the client to speak TLS to the proxy itself.

Confirm

env | grep -i '_proxy='

A proxy URL beginning https:// for a proxy that is not TLS

Fix

  • Write the proxy as http://proxy:PORT even in HTTPS_PROXY; the https site is still encrypted end to end through the tunnel.

Implicit TLS on a mail port that expects STARTTLS

Occasional

Port 587 (and 25) start in plain text and upgrade with STARTTLS; port 465 starts with TLS. Using implicit TLS (smtps://, an "SSL" option) against 587 gets the server's plain-text greeting.

Confirm

nc HOST 587

A readable greeting starting 220: the port starts in plain text

Fix

  • Use STARTTLS on 587 (in curl: smtp:// with --ssl-reqd), or implicit TLS on 465, as your mail provider documents.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

A TLS client expects the first bytes back to be a TLS record, which begins with a version number. When the server replies with ordinary text, such as an HTTP error page or a mail server's greeting, the client reads those bytes as a nonsense version and gives up. Nothing is wrong with certificates or TLS versions; the two ends are speaking different protocols.

Words used on this page

TLS record
The unit TLS data travels in. Its header includes a protocol version, which is what the client found wrong.
STARTTLS
Connecting in plain text and then switching to TLS, as mail on port 587 does. Implicit TLS (port 465, smtps://) starts encrypted instead.
HTTPS_PROXY
An environment variable naming the proxy for https requests. Its own scheme says how to talk to the proxy, not to the site.
What is actually happening

Reproduced on 22 October 2026 against local servers that answer in plain text. On Ubuntu 24.04 with OpenSSL 3.0.13, curl printed "curl: (35) OpenSSL/3.0.13: error:0A00010B:SSL routines::wrong version number", openssl s_client and Python 3.12 said the same in their own words, and the same curl error appeared for smtps:// against a mail port that greets in plain text.

The wording depends on the TLS library, not on the mistake. Ubuntu's git, built on GnuTLS, said "gnutls_handshake() failed: An unexpected TLS packet was received.", and curl on macOS, built on LibreSSL, said "tlsv1 alert protocol version". All of them were the same plain-text reply.

A proxy setting caused it too: curl with a proxy URL written https:// for a proxy that speaks plain HTTP failed with wrong version number, and the TLS error disappeared once the proxy URL was written http://.

How to tell this is your problem
WhereWhat you see
curl (OpenSSL 3)curl: (35) OpenSSL/3.0.13: error:0A00010B:SSL routines::wrong version number
Python[SSL: WRONG_VERSION_NUMBER] wrong version number
git on Ubuntu (GnuTLS)gnutls_handshake() failed: An unexpected TLS packet was received.
curl on macOS (LibreSSL)tlsv1 alert protocol version

How to know it is actually fixed

  • openssl s_client shows a certificate chain on the port you use, and the original request succeeds.

Stopping it happening again

  • Keep TLS ports and plain ports distinct and documented, and health-check the https URL itself, not just the port.
  • Write proxy variables as http:// unless your proxy really accepts TLS from clients.

Security considerations

  • Do not "fix" this by switching the URL to http:// on a link that crosses a network you do not trust: find the port that really serves TLS instead.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it