# 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.

Source: https://certguru.io/errors/wrong-version-number/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

> **The exact message**
>
> ```
>   error:0A00010B:SSL routines::wrong version number
>   ```
>   OpenSSL 1.1.1 spelled the same condition
>   `error:1408F10B:SSL routines:ssl3_get_record:wrong version number`. curl reports it as
>   `curl: (35) error:0A00010B:SSL routines::wrong version number`.

## 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.

> **The one-line diagnosis**
>
> Send a plaintext request to the same port. If it answers, the port is not a TLS port and there is
>   nothing to fix on the certificate side.

*Does the port answer plain HTTP?*

```bash
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*

```bash
openssl s_client -connect example.com:443 &1 | head -5
openssl s_client -connect example.com:8080 &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://`.

> **nginx: listen 443 without ssl**
>
> A `listen 443;` directive missing the `ssl` parameter makes nginx serve plain HTTP on the HTTPS
>   port, which produces this error for every visitor. It must read `listen 443 ssl;`.
>
>   ```nginx
> server {
>     listen 443 ssl;
>     server_name example.com;
>
>     ssl_certificate     /etc/ssl/certs/fullchain.crt;
>     ssl_certificate_key /etc/ssl/private/private.key;
> }
> ```

Check the config before reloading:

```bash
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*

```bash
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?*

```bash
for v in tls1_3 tls1_2 tls1_1 tls1; do
  printf '%-8s ' "$v"
  openssl s_client -"$v" -connect example.com:443 &1 \
    | grep -qE "Cipher *: *[A-Z]" && echo OK || echo "not accepted"
done
```

> **OpenSSL 3 refuses old versions by default**
>
> OpenSSL 3 sets a minimum security level that excludes TLS 1.0 and 1.1 outright, so those rows can
>   report a failure that is your own client's policy rather than the server's. Add
>   `-cipher DEFAULT@SECLEVEL=0` to test them, and treat any server that needs it as something to
>   replace rather than to accommodate.

## 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?*

```bash
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

- [RFC 8446 section 5.1: TLS record layer](https://www.rfc-editor.org/rfc/rfc8446#section-5.1) — the five-byte record header whose version field triggers this error
- [openssl-s_client(1)](https://docs.openssl.org/3.5/man1/openssl-s_client/) — the -starttls and version-pinning options used below

## Related pages

- https://certguru.io/errors/pem-routines-no-start-line/
- https://certguru.io/openssl/get-certificate-from-server/
- https://certguru.io/errors/curl-60-ssl-certificate-problem/
- https://certguru.io/openssl/check-tls-versions-and-ciphers/
- https://certguru.io/platforms/nginx/
- https://certguru.io/platforms/haproxy/
- https://certguru.io/errors/x509-signed-by-unknown-authority-go/
- https://certguru.io/concepts/certificate-chain/

--
CertGuru — https://certguru.io
