wrong version number

The OpenSSL error "SSL routines::wrong version number" means the bytes arriving from the server are not a TLS record. OpenSSL reads the first five bytes of every response as a record header, finds a protocol version field containing something that is not a TLS version, and reports it as a wrong version number. In the overwhelming majority of cases the server is not speaking TLS on that port at all — an HTTPS request has been sent to a plaintext HTTP port, to a port where a proxy or load balancer has already terminated TLS, or to a service such as SMTP or PostgreSQL that requires a STARTTLS upgrade first. Confirm which by re-requesting the same port over plain HTTP instead of HTTPS; if the plaintext request succeeds, the port is not a TLS port.

Updated August 19, 2026 Verified against OpenSSL 3.5.7

What the error actually means #

Every TLS message begins with a five-byte record header, and bytes two and three of that header are the protocol version. OpenSSL reads those bytes before anything else. When they contain a value that is not a TLS version, it stops and reports a wrong version number.

The important part is what usually occupies those bytes instead. A plaintext HTTP response starts HTTP/1.1 200, and the parser reads TT as the version field. This is not a negotiation failure — it is OpenSSL telling you that whatever answered is not speaking TLS.

Does the port answer plain HTTP?
curl -v http://example.com:8443/

The four causes, in order of frequency #

1. Connecting to a plaintext port #

https:// against a port serving plain HTTP. Common shapes: port 80 addressed as HTTPS, an application server on 8080 fronted by a proxy that terminates TLS, or a container port mapping that points at the wrong listener.

Confirm which ports actually speak TLS
openssl s_client -connect example.com:443 </dev/null 2>&1 | head -5
openssl s_client -connect example.com:8080 </dev/null 2>&1 | head -5

A port speaking TLS returns CONNECTED followed by certificate output. A plaintext port returns CONNECTED and then this error, because the connection succeeds and the parse does not.

2. TLS terminated somewhere upstream #

Behind a load balancer, an ingress controller or a CDN, TLS often ends at the edge and the backend is contacted over plain HTTP. Anything that connects to the backend directly — a health check, a sidecar, a debugging session on the pod — must use http://.

Check the config before reloading:

sudo nginx -t && sudo nginx -s reload

3. The protocol needs STARTTLS #

Mail, LDAP and several database protocols open in plaintext and negotiate TLS in-band. Opening with a ClientHello is a protocol error, and OpenSSL reports it as this. Ask s_client to perform the upgrade instead:

STARTTLS by protocol
openssl s_client -starttls smtp  -connect mail.example.com:587
openssl s_client -starttls imap  -connect mail.example.com:143
openssl s_client -starttls ldap  -connect ldap.example.com:389
openssl s_client -starttls postgres -connect db.example.com:5432

Ports 465 (SMTPS), 993 (IMAPS) and 636 (LDAPS) are implicit-TLS ports and need no -starttls. Using the flag on those produces the mirror-image failure.

4. A genuine version mismatch #

Rare, and it looks different: a handshake starts and then fails. It is worth checking only after the first three are ruled out, typically against old appliances that support nothing above TLS 1.0.

Which versions does the server accept?
for v in tls1_3 tls1_2 tls1_1 tls1; do
printf '%-8s ' "$v"
openssl s_client -"$v" -connect example.com:443 </dev/null 2>&1 \
  | grep -qE "Cipher *: *[A-Z]" && echo OK || echo "not accepted"
done

A quick way to see what really answered #

If the cause is still not obvious, read the first bytes off the socket without a TLS parser in the way. Plaintext will be legible; a real TLS server returns binary starting with 0x16 0x03:

What does the port send back?
printf 'GET / HTTP/1.0\r\n\r\n' | timeout 5 nc example.com 8443 | head -3
Expected output
HTTP/1.1 400 Bad Request
Server: nginx
Content-Type: text/html

Legible HTTP headers confirm cause 1 or 2 and end the investigation.

Frequently asked questions

What is the OpenSSL 1.1.1 form of this error?
error:1408F10B:SSL routines:ssl3_get_record:wrong version number. OpenSSL 3 dropped the function name from error strings, so the same condition reads error:0A00010B:SSL routines::wrong version number. The cause and the fix are identical.
Does this mean my TLS version is too old?
Usually not, despite the wording. A genuine version mismatch produces no protocols available or unsupported protocol. This message means the response did not parse as a TLS record at all, which is what plaintext looks like to a TLS parser.
Why do I get this against my own nginx server?
The most common cause is a listen directive missing the ssl parameter, so nginx is serving plain HTTP on 443. The second is a second server block listening on the same port without TLS, which wins the address match.
I get this connecting to PostgreSQL or MySQL. Why?
Those protocols begin in plaintext and upgrade to TLS in-band, so a client that opens with a TLS ClientHello is talking to a plaintext listener. Use the driver's own TLS mode — sslmode=require for PostgreSQL — rather than wrapping the socket yourself.
Could a proxy cause this?
Yes, and it is easy to miss. If HTTPS_PROXY is set to a plain-http URL and the client connects directly rather than issuing a CONNECT, the proxy answers with a plaintext HTTP error page, which the client then tries to parse as TLS.

References