# [SSL: WRONG_VERSION_NUMBER]：Python 报错原因与修法

> WRONG_VERSION_NUMBER 表示 Python 用 TLS 去连一个返回明文的端口：代理地址写成了 https://、端口用错，或服务器那个端口没开 TLS。

Source: https://ssl.sh/zh/errors/python-ssl-wrong-version-number/
Last updated: 2026-11-10

**`[SSL: WRONG_VERSION_NUMBER] wrong version number` 的意思是：Python 发起了 TLS 握手，但对方回的不是 TLS。** 那个端口上跑的是明文 HTTP 或其他明文协议，最常见的情况是代理地址写成了 `https://`、代码用错了端口，或者服务器在你以为的端口上没有启用 TLS。

## 快速修复

多数情况把代理地址从 `https://` 改成 `http://` 就能修好。先确认目标端口实际在说什么协议，以及 Python 用了哪个代理：

```bash
# 这个端口是不是在回明文 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://`。这里的协议指的是“你和代理之间怎么通信”，而不是目标网站用什么协议：

```bash
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](https://ssl.sh/zh/glossary/starttls/)”的端口，会立刻失败。

- **确认：** URL 或代码用的是 80、25、587、143、5432 等端口，却用了只会 TLS 的客户端。
- **修复：** 按端口选用对应写法：

```python
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 的[登录页](https://ssl.sh/zh/errors/captive-portal-ssl-error/)和一些过滤网关会拦截 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 检测工具](https://ssl.sh/zh/tools/ssl-checker/)复查；邮件服务器用 [STARTTLS 检测工具](https://ssl.sh/zh/tools/starttls-checker/)。

## 不要这样做

这三种做法要么让数据明文传输，要么对这个错误无效。

- **把地址改成 `http://` 让报错消失。** 请求会以明文传输，token、密码一并暴露。
- **`verify=False`。** 对这个错误没有作用，反而让同一段代码之后的所有请求都失去证书校验。
- **强制使用旧协议**，例如 `ssl.PROTOCOL_TLSv1`。问题不在 TLS 版本，而在对方没有 TLS；降级只会削弱本来正常的连接。
