Error message
[SSL: WRONG_VERSION_NUMBER] wrong version number
Also printed as
- ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number
Followed by a source location such as "(_ssl.c:1082)". urllib shows it inside "<urlopen error …>", requests and urllib3 inside "Max retries exceeded … (Caused by SSLError(…))".
Python ssl module (OpenSSL) — ssl.SSLError, SSL_R_WRONG_VERSION_NUMBER
What it means
Python started a TLS handshake, and the bytes that came back are not a TLS record: OpenSSL reads them as a record header with an impossible protocol version. In practice the peer speaks plain text on that port — an HTTP server, a proxy expecting plain HTTP, or an SMTP server waiting for its greeting exchange before STARTTLS.
Why it happens
https:// to a plain-HTTP port
commonDevelopment servers, internal services behind a TLS-terminating load balancer and metrics endpoints often listen on plain HTTP. Using https:// with their port produces this error immediately.
The proxy is configured as https:// but speaks HTTP
commonHTTPS_PROXY=https://proxy:3128 or proxies={"https": "https://…"} makes urllib3 start TLS with the proxy. Since urllib3 1.26 that is honoured literally, and most corporate proxies only accept plain HTTP from clients.
smtplib.SMTP_SSL on a STARTTLS port
commonPort 587 (and 25) begins in plain text and upgrades with STARTTLS; implicit TLS lives on 465. SMTP_SSL against 587 fails this way, as does IMAP4_SSL against 143.
A captive portal or filter answers in plain HTTP
occasionalHotel or guest Wi-Fi and some security appliances intercept the connection and reply with an HTTP page.
How to fix it
- 1.
Find out what the port speaks
Request the port with http:// and try a TLS handshake with openssl s_client; then use the matching scheme or the correct port.
bash curl -si http://127.0.0.1:8080/ | head -1 # an HTTP status line: the port is plain HTTP openssl s_client -connect 127.0.0.1:8443 -brief </dev/null # a TLS summary: the port speaks TLS - 2.
Write proxy URLs with http://
The https entry of the proxies mapping names the proxy for HTTPS destinations; its URL scheme describes how to talk to the proxy itself. Traffic to the destination is still end-to-end TLS.
python import requests # Most forward proxies take plain HTTP from the client and tunnel TLS with CONNECT proxies = {"https": "http://proxy.example.net:3128"} # http:// here, not https:// requests.get("https://api.example.com/", proxies=proxies) - 3.
Mail: match the class to the port
Use SMTP + starttls() on 587 and SMTP_SSL on 465; the same rule applies to imaplib and poplib.
python import smtplib, ssl ctx = ssl.create_default_context() # Port 587: plain connection first, then STARTTLS with smtplib.SMTP("mail.example.com", 587) as s: s.starttls(context=ctx) # Port 465: implicit TLS from the first byte with smtplib.SMTP_SSL("mail.example.com", 465, context=ctx) as s: s.noop()
Related errors
curl: (35) TLS connect error: error:0A00010B:SSL routines::wrong version numbercurl (OpenSSL backend)SSL routines:tls_validate_record_header:wrong version numberOpenSSL 3 (openssl s_client and any OpenSSL-based client)code: 'ERR_SSL_WRONG_VERSION_NUMBER'Node.js tls.connect / fetch (undici)[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocolPython ssl module (OpenSSL)