• Python
  • Exact text checked with the real tool

[SSL: WRONG_VERSION_NUMBER] wrong version number

Also seen in: requests, urllib3, pip, httpx, aiohttp, smtplib, imaplib

In one sentence

Python began a TLS handshake but got no TLS reply: usually an https URL for a plain-HTTP port, an https:// proxy URL, or SMTP_SSL on a STARTTLS port.

Where you see it

In Python’s ssl module and everything built on it: urllib, requests, urllib3, pip, httpx, aiohttp and smtplib. The text in brackets is OpenSSL’s reason code; the number after _ssl.c: changes with the Python version.

ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1028)

Python linked with most OpenSSL 3.2 to 3.6 releases reports the same situation as [SSL] record layer failure, for example ssl.SSLError: [SSL] record layer failure (_ssl.c:1032). OpenSSL 3.3.7, 3.4.5, 3.5.6, 3.6.2, 4.0 and later say wrong version number again.

requests wraps it:

requests.exceptions.SSLError: HTTPSConnectionPool(host='api.example.com', port=8080): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1028)')))

What it means

Python sent the first message of a TLS handshake and expected a TLS record back. The reply did not look like TLS at all: often it was a plain-HTTP answer or a mail server’s greeting, whose first bytes OpenSSL reads as a nonsense TLS version. Despite the name, this is almost never about TLS versions. Your certificates and CA bundle are not involved; the connection went to something that does not speak TLS on that port.

Likely causes

An https URL for a port that speaks plain HTTP

A development server, an internal service or a health endpoint that listens with plain HTTP, called with https://. Confirm: a plain request works, and OpenSSL shows the same reason:

curl -v http://api.example.com:8080/
openssl s_client -connect api.example.com:8080 -servername api.example.com </dev/null

Fix: use http:// for that service if it is on a trusted local network, or, better, enable TLS on it, or use the port where it does speak TLS (often 443 or 8443).

The proxy URL starts with https://

A very common cause behind company proxies. HTTPS_PROXY=https://proxy.example.com:3128 tells Python to speak TLS to the proxy, but most proxies expect plain HTTP there, even for HTTPS traffic. Recent urllib3 versions say so directly:

requests.exceptions.ProxyError: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/ (Caused by ProxyError('Unable to connect to proxy. Your proxy appears to only use HTTP and not HTTPS, try changing your proxy URL to be HTTP. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#https-proxy-error-http-proxy', SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1028)'))))

pip shows the same inside a WARNING: Retrying … line. Confirm the proxy settings:

env | grep -i proxy
pip config list
Get-ChildItem Env: | Where-Object Name -like '*proxy*'

Fix: use http:// in the proxy URL; the traffic to the website stays encrypted end to end inside the proxy tunnel:

export HTTPS_PROXY=http://proxy.example.com:3128

If the proxy also inspects TLS, you will next need its root certificate: How to work behind a TLS-inspecting corporate proxy, safely.

SMTP_SSL on a STARTTLS port

Mail servers use two styles. Port 465 speaks TLS from the first byte (smtplib.SMTP_SSL). Port 587, and 25, start in plain text and switch to TLS with the STARTTLS command (smtplib.SMTP plus starttls()). SMTP_SSL on port 587 reads the server’s plain-text greeting as a TLS record and fails with this error.

Fix: match the class to the port:

import os, smtplib, ssl

context = ssl.create_default_context()
with smtplib.SMTP("smtp.example.com", 587) as smtp:
    smtp.starttls(context=context)
    smtp.login("user@example.com", os.environ["SMTP_PASSWORD"])

The same applies to imaplib.IMAP4_SSL (port 993) versus IMAP4 with starttls() (port 143). See the smtplib documentation.

Something in between answers instead

A captive portal, firewall or proxy may answer with its own plain-text or HTTP block page. If the same code works on another network, this is the cause; talk to the network’s owner.

Check yours

Try it: Paste your errorOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

The whole message or just the important line. A stack trace is fine; we look for the known parts.

Paste the full traceback, including any ProxyError text. The finder shows the matching page, so you can tell a protocol mix-up from a certificate problem.

Learn why

In the gameLevel 15: The handshakeThe Barbican, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9