Python [SSL: WRONG_VERSION_NUMBER] wrong version number (tested)

ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number almost never means a TLS version mismatch. We reproduced it with urllib and requests: it happens when the client speaks TLS and the other side answers in plain HTTP -- an https:// URL pointed at an HTTP-only port, or a proxy URL written as https:// when the proxy itself speaks HTTP. OpenSSL reads the server's HTTP/1.1 text as a TLS record and rejects its "version".

1. Reproduce it: https:// to a port that speaks plain HTTP

import socket, threading, time, ssl, urllib.request, requests

PORT = 8963
def serve():   # a plain-HTTP server (no TLS)
    srv = socket.socket()
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("127.0.0.1", PORT))
    srv.listen()
    while True:
        conn, _ = srv.accept()
        conn.recv(65536)
        conn.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
        conn.close()
threading.Thread(target=serve, daemon=True).start()
time.sleep(0.2)

url = f"https://127.0.0.1:{PORT}/"          # https:// to a port that speaks plain HTTP
try:
    urllib.request.urlopen(url)
except Exception as e:
    print("urlopen  ->", type(e).__name__ + ":", e)
try:
    requests.get(url)
except Exception as e:
    print("requests ->", type(e).__name__ + ":", e)
print("http://  ->", requests.get(url.replace("https://", "http://")).status_code)
print(ssl.OPENSSL_VERSION)

# The proxy variant: proxy URL written with https:// for a proxy that speaks plain HTTP
for proxy in ("https://127.0.0.1:8963", "http://127.0.0.1:8963"):
    try:
        requests.get("https://example.com/", proxies={"https": proxy}, timeout=5)
    except Exception as e:
        print(f"proxy {proxy:24} ->", type(e).__name__ + ":", str(e)[-90:])
urlopen  -> URLError: <urlopen error [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1029)>
requests -> SSLError: HTTPSConnectionPool(host='127.0.0.1', port=8963): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1029)')))
http://  -> 400
OpenSSL 3.5.7 9 Jun 2026
proxy https://127.0.0.1:8963   -> ProxyError:  SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1029)'))))
proxy http://127.0.0.1:8963    -> ProxyError: Error('Unable to connect to proxy', OSError('Tunnel connection failed: 400 Bad Request')))

Measured: the client started a TLS handshake, the server answered with a plain-text HTTP/1.1 400 ... line, and OpenSSL tried to read those bytes as a TLS record header. "HT" is not a valid TLS version, hence WRONG_VERSION_NUMBER. The same URL with http:// reached the server fine. Nothing is wrong with your TLS version settings or certificates.

2. The usual causes

3. Check what the port actually speaks

curl -sv http://HOST:PORT/ -o /tmp/out    # answers? it's plain HTTP
openssl s_client -connect HOST:PORT -brief </dev/null   # TLS servers print the protocol and cipher

If plain http:// works, fix the URL or the proxy setting. If neither works, the service on that port is not HTTP at all.

Related: ERR_SSL_PROTOCOL_ERROR in Chrome, the browser-side version of the same mismatch.

Frequently asked questions

Does WRONG_VERSION_NUMBER mean I need a different TLS version?

Almost never. We reproduced it by sending a TLS handshake to a plain-HTTP server: OpenSSL read the server's 'HTTP/1.1 400' text as a TLS record. Changing ssl minimum/maximum versions doesn't help.

Why does requests show it through a proxy?

If HTTPS_PROXY or proxies= says https://proxy:port, requests speaks TLS to the proxy. Most proxies expect plain HTTP and tunnel with CONNECT, so write the proxy as http://proxy:port. We measured both forms.

I get it with smtplib. Why?

smtplib.SMTP_SSL expects TLS from the first byte (port 465). Port 587 starts in plain text: use smtplib.SMTP(host, 587) and call .starttls().

How do I tell if a port speaks HTTP or HTTPS?

Try curl -sv http://HOST:PORT/ -- if it answers, it's plain HTTP. openssl s_client -connect HOST:PORT -brief shows a TLS handshake if the port speaks TLS.

Should I set verify=False?

No -- it doesn't help here (the failure is before certificate checks) and it removes protection you need. Fix the URL scheme or proxy setting instead.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 (OpenSSL 3.5.7), urllib.request and requests 2.32.3, connecting with https:// to a local plain-HTTP socket server, directly and through a proxies= setting. PlainHeaders is our extension; Python, requests and OpenSSL are free.