多数情况下,CERTIFICATE_VERIFY_FAILED 不是代码写错了,而是 Python 的证书信任链缺失或过期;优先用 certifi 更新根证书,而非关闭验证。

直接说结论:多数情况下,CERTIFICATE_VERIFY_FAILED 不是代码写错了,而是 Python 的证书信任链缺失或过期;优先用 certifi 更新根证书,而非关闭验证。
为什么 certifi 更新能解决大部分 CERTIFICATE_VERIFY_FAILED
Python 自带的 ssl 模块默认不读系统 CA 证书(比如 macOS 的钥匙串、Linux 的 /etc/ssl/certs),而是依赖 certifi 提供的 PEM 文件。这个文件如果陈旧(例如多年未更新),就无法验证新签发的 Let’s Encrypt 或 Sectigo 证书。
-
certifi是 Requests 等主流库默认使用的证书包,位置可通过certifi.where()查看 - 运行
pip install -U certifi会覆盖旧 PEM,无需改代码 - 验证是否生效:执行
python -c "import requests; print(requests.get('https://httpbin.org/get').status_code)"—— 成功返回 200 即说明证书链已通 - 某些企业环境会拦截 HTTPS 流量并替换证书,此时即使 certifi 最新也会失败,需额外导入内部 CA
忽略 SSL 验证的几种写法及风险差异
绕过验证只是临时调试手段,生产环境禁用;不同方式影响范围和隐蔽性不同:
- Requests 全局禁用:
requests.packages.urllib3.disable_warnings()+verify=False参数,但只对当前 session 生效 - Requests 单次请求:
requests.get(url, verify=False)—— 最常见,但会触发InsecureRequestWarning - urllib(Python 3.6+):
urllib.request.urlopen(url, context=ssl._create_unverified_context()),比 Requests 更底层,容易漏掉上下文传递 - 设置环境变量:
export PYTHONHTTPSVERIFY=0(Linux/macOS)或set PYTHONHTTPSVERIFY=0(Windows),影响整个 Python 进程,极易被遗忘
所有忽略验证的方式都会让中间人攻击(MITM)完全失效,且无法捕获证书过期、域名不匹配等真实问题。
立即学习“Python免费学习笔记(深入)”;
企业内网或自签名证书的正确处理方式
如果你明确知道目标服务使用的是公司内部 CA 或自签名证书,应显式加载对应证书,而不是一刀切禁用验证:
- 把内部 CA 证书(.pem 或 .crt)保存为文件,例如
/path/to/internal-ca.pem - Requests 中使用:
requests.get(url, verify="/path/to/internal-ca.pem") - 若需同时信任系统证书 + 内部证书,可合并 PEM:
cat $(python -c "import certifi; print(certifi.where())") /path/to/internal-ca.pem > combined.pem,再传给verify=combined.pem - 注意路径权限:确保运行 Python 的用户有读取证书文件的权限,否则报
IOError: [Errno 13] Permission denied
真正麻烦的不是找不到绕过方法,而是误以为 verify=False 是解决方案——它只是把证书错误藏起来了。下次遇到 CERTIFICATE_VERIFY_FAILED,先查 certifi.where() 输出的路径,用 openssl x509 -in <path> -text -noout | grep "Not After"</path> 看证书有效期,比加一行 verify=False 有用得多。


















