# UNSAFE_LEGACY_RENEGOTIATION_DISABLED：Python 报错怎么修

> OpenSSL 3 拒绝连接不支持 RFC 5746 安全重协商的服务器。正解是升级服务器；临时只对这一台主机放开旧式连接，并保持证书校验开启。

Source: https://ssl.sh/zh/errors/python-unsafe-legacy-renegotiation-disabled/
Last updated: 2026-11-10

**`[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled` 的意思是：服务器不支持 RFC 5746 规定的安全重协商，你的客户端用的 OpenSSL 3 拒绝和它建立连接。** 证书本身没有问题。问题出在服务器，或者它前面的负载均衡、防火墙、HTTPS 解密设备，用的 TLS 实现早于 2010 年的那次修补，而 OpenSSL 3.0 默认不再迁就这种情况。

## 这条报错是什么意思

这是 OpenSSL 3 对未打 RFC 5746 补丁的服务器的默认拒绝。2009 年，TLS 重协商被发现存在漏洞（CVE-2009-3555），攻击者可以把自己的数据拼接到受害者会话的前面。2010 年 2 月发布的 RFC 5746 用 `renegotiation_info` 扩展修补了它：打过补丁的客户端和服务器会互相证明，重协商属于同一条连接。从来不发这个扩展的服务器就叫“未打补丁”。

OpenSSL 文档写明：如果 `SSL_OP_LEGACY_SERVER_CONNECT` 和 `SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION` 都没设置，和未打补丁服务器的首次连接就会失败。OpenSSL 1.1.1 默认设置了前者，OpenSSL 3.0 起不再默认设置。所以这个错误通常出现在换到 Ubuntu 22.04（自带 OpenSSL 3.0.2）、更新 Docker 基础镜像、或者换成链接 OpenSSL 3 的 Python 之后，而服务器可能多年没动过。

在 Python 里它经常被包在别的报错里，比如 [requests 的 Max retries exceeded](https://ssl.sh/zh/errors/python-requests-max-retries-ssl-error/)，AWS CLI 的输出里也会出现。其他基于 OpenSSL 3 的客户端会显示同一个原因：`error:0A000152:SSL routines::unsafe legacy renegotiation disabled`。

## 常见原因（按出现频率排序）

这个报错有 3 种情况：服务器或设备老旧（最常见）、链路上的 HTTPS 解密设备没打补丁、客户端刚换成 OpenSSL 3。

### 1. 老旧服务器或设备不支持 RFC 5746

多年未升级的应用服务器、嵌入式设备、老式硬件负载均衡、过时的 WAF 最常见，对接长期没人维护的老接口时容易碰到。

- **确认：** 只在测试命令里临时打开旧式连接，看重协商那一行：

```bash
openssl version
openssl s_client -connect example.com:443 -servername example.com -legacy_server_connect </dev/null 2>/dev/null | grep -i renegotiation
# Secure Renegotiation IS NOT supported  -> 服务器未打补丁
```

- **修复：** 升级服务器的 TLS 库或设备固件。2010 年以后还在维护的 TLS 实现都支持 RFC 5746。启用 TLS 1.3 也能解决支持 TLS 1.3 的客户端的问题，因为 TLS 1.3 完全禁止重协商。

### 2. 链路上有 HTTPS 解密设备

如果 `openssl s_client` 显示的签发者是公司的上网行为管理、安全网关厂商，而不是公开 CA，那么“未打补丁”的是这台设备，而不是目标网站。见[企业 SSL 解密导致的报错](https://ssl.sh/zh/errors/corporate-ssl-inspection/)。

- **修复：** 请网络管理员升级设备，或者把目标域名加入解密白名单。

### 3. 客户端换成了 OpenSSL 3

这是触发条件而不是根因，但它解释了“昨天还好好的”。查看 Python 用的 OpenSSL 版本：

```bash
python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
```

### 怎么和对方沟通

如果服务器不归你管，把下面几样东西发给对方的运维：报错原文、你这边的 OpenSSL 版本、上面那条带 `-legacy_server_connect` 的检测命令和它的输出。说明问题是“服务器不支持 RFC 5746 安全重协商”，而不是证书过期或域名不对，可以省去很多来回沟通。对方升级之前，你这边的临时方案要写清楚负责人和预计删除日期。

## 分平台修复命令

首选是修服务器。在等待对方升级期间，只对这一个目标放开旧式连接，并且保持证书校验开启。

**Python 3.12 及以上，配合 requests，只作用于一台主机：**

```python
import ssl
import requests
from requests.adapters import HTTPAdapter

class LegacyServerConnectAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        ctx = ssl.create_default_context()          # 证书校验仍然开启
        ctx.options |= getattr(ssl, "OP_LEGACY_SERVER_CONNECT", 0x4)
        kwargs["ssl_context"] = ctx
        return super().init_poolmanager(*args, **kwargs)

session = requests.Session()
session.mount("https://legacy.example.com/", LegacyServerConnectAdapter())
print(session.get("https://legacy.example.com/", timeout=10).status_code)
```

Python 3.11 及更早版本没有这个常量，`0x4` 是 OpenSSL 头文件里 `SSL_OP_LEGACY_SERVER_CONNECT` 的值，所以代码里用 `getattr` 兜底。

**任何链接 OpenSSL 3 的程序（Python，以及用 OpenSSL 编译的 curl、git），Linux 或 macOS 上只对一个进程生效：**

```ini
# /etc/ssl/openssl-legacy-connect.cnf
openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyServerConnect
```

```bash
OPENSSL_CONF=/etc/ssl/openssl-legacy-connect.cnf python3 job.py
```

`OPENSSL_CONF` 只替换这个进程的配置，不要用它覆盖 `/etc/ssl/openssl.cnf`。

**Node.js 17 及以上**（同样基于 OpenSSL 3）：

```javascript
const https = require('node:https');
const { constants } = require('node:crypto');
https.get('https://legacy.example.com/', { secureOptions: constants.SSL_OP_LEGACY_SERVER_CONNECT }, res => console.log(res.statusCode));
```

**Windows：** python.org 安装包自带 OpenSSL，上面的 Python 写法同样适用。

**国内环境：** 对接银行、政务或老 ERP 接口时，对方往往不能马上升级。可以先确认 TLS 终止点在哪里：如果对方前面有云厂商的负载均衡或 CDN，让对方把证书和 TLS 终止挪到负载均衡上，通常比升级老后端快得多。在等待期间，把上面的临时方案限定在一个独立的脚本或容器里运行，不要放进公共的基础镜像。

## 不推荐：关闭证书校验

`verify=False`、`CERT_NONE`、`curl -k` 对这个错误没有用：握手在检查证书之前就失败了。加上它们只会去掉防冒充的保护，链路上任何人都能截获 token 和密码，而程序照样报错。

同样不要用范围更大的 `UnsafeLegacyRenegotiation` / `SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION`，也不要修改全系统的 OpenSSL 配置，那会削弱这台机器上所有 TLS 客户端的安全性。更安全的做法是修服务器，或者只在一个上下文、一台主机上设置 `OP_LEGACY_SERVER_CONNECT`，证书校验保持开启，服务器升级后立刻删掉。把它当作有到期日的临时例外来管理。

## 怎么确认已经修好

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | grep -E "Secure Renegotiation|Protocol"
```

服务器修好后，**不加** `-legacy_server_connect` 也能连上，并显示 `Secure Renegotiation IS supported`（TLS 1.2）或协商出 `TLSv1.3`。然后删掉客户端的临时代码，用默认设置重新跑一遍 Python 程序。也可以用 [SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)查看服务器支持的协议版本和加密套件，用 [TLS 配置生成器](https://ssl.sh/zh/tools/tls-config-generator/)生成一份现代的服务器配置。想了解重协商在握手中的位置，可以读 [TLS 握手过程](https://ssl.sh/zh/learn/tls-handshake/)。
