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.
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.
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.
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:
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.
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:
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 readserror: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 availableorunsupported 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
listendirective missing thesslparameter, 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=requirefor PostgreSQL — rather than wrapping the socket yourself. - Could a proxy cause this?
- Yes, and it is easy to miss. If
HTTPS_PROXYis set to a plain-httpURL 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
- RFC 8446 section 5.1: TLS record layer — the five-byte record header whose version field triggers this error
- openssl-s_client(1) — the -starttls and version-pinning options used below