编程语言
[SSL: WRONG_VERSION_NUMBER]:Python 报错原因与修法
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number[SSL: WRONG_VERSION_NUMBER] wrong version number 的意思是:Python 发起了 TLS 握手,但对方回的不是 TLS。 那个端口上跑的是明文 HTTP 或其他明文协议,最常见的情况是代理地址写成了 https://、代码用错了端口,或者服务器在你以为的端口上没有启用 TLS。
快速修复
多数情况把代理地址从 https:// 改成 http:// 就能修好。先确认目标端口实际在说什么协议,以及 Python 用了哪个代理:
# 这个端口是不是在回明文 HTTP?
curl -sv http://example.com:443/ -o /dev/null
# Python 会使用哪些代理?
python3 -c "import urllib.request; print(urllib.request.getproxies())"
env | grep -i _proxy
- 代理地址以
https://开头的,改成http://。这里的协议指的是“你和代理之间怎么通信”,而不是目标网站用什么协议:
export HTTPS_PROXY=http://proxy.example.com:8080
export HTTP_PROXY=http://proxy.example.com:8080
curl http://...:443/能拿到 HTTP 响应,说明这个端口在跑明文,去修服务器或换正确的端口。- 邮件等其他协议,要让客户端类和端口对应(见下文)。
原因
requests、pip 等基于 urllib3 的工具最常见的原因是明文 HTTP 代理被写成了 https:// 地址,另外还有 3 种情况。
1. 明文 HTTP 代理用了 https:// 地址
公司代理、抓包工具(Fiddler、Charles、mitmproxy)以及各种本地代理软件,端口上一般接受的是明文 HTTP,再用 CONNECT 隧道转发 HTTPS。
- 确认:
getproxies()输出里有'https': 'https://...',或者设置NO_PROXY=example.com后报错消失。 - 修复:
HTTPS_PROXY、代码里的proxies=字典、pip --proxy都改用http://。Windows 上 Python 还会从注册表读取系统代理设置,本地代理软件开启“系统代理”时也要检查那里写的格式。
2. 用 TLS 客户端连了明文端口
直接 TLS 的客户端连到“先明文再 STARTTLS”的端口,会立刻失败。
- 确认: URL 或代码用的是 80、25、587、143、5432 等端口,却用了只会 TLS 的客户端。
- 修复: 按端口选用对应写法:
import smtplib
# 465 端口:直接 TLS
with smtplib.SMTP_SSL("smtp.example.com", 465) as s:
s.login(user, password)
# 587 端口:先明文,再 STARTTLS
with smtplib.SMTP("smtp.example.com", 587) as s:
s.starttls()
s.login(user, password)
IMAP 同理:imaplib.IMAP4_SSL 对应 993 端口,IMAP4 加 starttls() 对应 143 端口。
3. 服务器在这个端口上没开 TLS
Web 服务器在 443 端口监听却没启用 TLS,或者负载均衡把明文 HTTP 转发到客户端以为是 HTTPS 的端口,也会报同样的错。
- 确认:
openssl s_client -connect example.com:443 -servername example.com同样报wrong version number,而curl http://example.com:443/返回了 HTML。 - 修复: Nginx 的
listen要带ssl参数(listen 443 ssl;),并配置ssl_certificate和ssl_certificate_key。宝塔面板里开启 SSL 后会自动写好这一行,手工改过配置文件的要重点检查。阿里云 SLB、腾讯云 CLB 的监听协议选成 HTTP 而前端用 HTTPS 访问,也是同样的现象。
4. 认证门户或中间设备用明文应答
酒店、机场 Wi-Fi 的登录页和一些过滤网关会拦截 443 端口,并返回 HTTP 跳转。
- 修复: 先完成门户登录,或换一个网络测试确认。
各环境修法
客户端工具的修法一致:代理地址用 http://,目标地址保持 https://;服务器端要在监听端口上启用 TLS。
pip: pip install --proxy http://proxy.example.com:8080 package,或一次性写入配置 pip config set global.proxy http://proxy.example.com:8080。使用清华、阿里云等国内镜像源时,镜像地址本身保持 https://,只有代理地址用 http://。
requests: requests.get(url, proxies={"https": "http://proxy.example.com:8080"})。requests 默认也会读取环境变量,除非设置 session.trust_env = False。
Docker 和 CI: 代理变量常由 Runner 或 Docker 守护进程配置自动注入。在容器里打印环境变量,看 Python 实际拿到的值。
服务器端: 修好监听配置后,用 SSL 检测工具复查;邮件服务器用 STARTTLS 检测工具。
不要这样做
这三种做法要么让数据明文传输,要么对这个错误无效。
- 把地址改成
http://让报错消失。 请求会以明文传输,token、密码一并暴露。 verify=False。 对这个错误没有作用,反而让同一段代码之后的所有请求都失去证书校验。- 强制使用旧协议,例如
ssl.PROTOCOL_TLSv1。问题不在 TLS 版本,而在对方没有 TLS;降级只会削弱本来正常的连接。
常见问题
verify=False 能解决 WRONG_VERSION_NUMBER 吗?
不能。握手在服务器发证书之前就失败了,没有证书可校验。关掉校验只会多一个安全问题。应该去改代理地址、端口或服务器配置。
为什么升级 pip 或 requests 后才开始报这个错?
pip 和 requests 依赖的 urllib3 新版本支持 HTTPS 代理,https:// 开头的代理地址会被当真:Python 先和代理本身建立 TLS 连接。代理如果只监听明文 HTTP,返回的就不是 TLS,于是报 WRONG_VERSION_NUMBER。把变量改成 http://proxy.example.com:8080 即可。
smtplib 哪个类配哪个端口?
465 端口是直接 TLS,用 smtplib.SMTP_SSL(host, 465);587 端口(以及通常的 25 端口)先明文再用 STARTTLS 升级,用 smtplib.SMTP(host, 587) 后调用 starttls()。QQ 邮箱、163 邮箱、阿里云企业邮箱的 SMTP 都支持 465 端口;另外很多云服务器默认封禁出站 25 端口,发信时优先用 465。
curl 遇到同样问题时是什么报错?
curl 会输出 curl: (35),OpenSSL 信息里带 wrong version number,原因相同,见 curl (35) SSL connect error。