编程语言
CERTIFICATE_VERIFY_FAILED:Python 证书验证失败的原因与修法
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed 的意思是:Python 连上了服务器,但无法用自己信任的 CA 证书验证服务器证书。 绝大多数情况要么是服务器没发中间证书,要么是当前 Python 环境用的 CA 证书包是空的、过旧的或者不对。
快速修复
服务器证书链不全就修服务器,链完整就修 Python 的 CA 证书包。先在命令行测试服务器:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
如果只返回一张证书,结果是 Verify return code: 21 或 20,说明服务器证书链不完整,要去服务器上修(见缺少中间证书)。如果证书链完整,就修客户端:
# 查看 Python 的 OpenSSL 去哪里找 CA 证书
python3 -c "import ssl; print(ssl.get_default_verify_paths())"
# requests:指定包含所需根证书的证书包
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
# urllib / ssl:让 OpenSSL 使用这个证书包
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
macOS 上用 python.org 安装包装的 Python,每个版本都要运行一次 /Applications/Python 3.x/Install Certificates.command。
原因
这个报错有 6 种原因,访问公网网站时最常见的是服务器证书链不完整。
1. 服务器证书链不完整
OpenSSL 需要服务器把中间证书一起发过来,它不会自己去下载。
- 确认:
openssl s_client -showcerts只显示depth=0,或者 SSL 检测工具提示证书链有问题。 - 修复: 服务器配置完整证书链(Nginx 用
fullchain.pem,宝塔里“证书”一栏要粘贴含中间证书的内容)。只在客户端打补丁不能根治。
2. Python 找不到任何 CA 证书
python.org 的 macOS 安装包自带一份 OpenSSL,但在运行 Install Certificates.command 之前没有 CA 证书包。自己编译的 Python、精简容器镜像和一些嵌入式发行版也会这样。
- 确认:
ssl.get_default_verify_paths()返回cafile=None,capath为空或目录不存在。 - 修复: 运行 Install Certificates.command,或安装系统的
ca-certificates包,或用SSL_CERT_FILE指向一个有效的证书包。
3. requests 看不到企业根证书或内网根证书
requests 只用 certifi,里面只有 Mozilla 收录的公开根证书。IT 部门通过域策略装进 Windows 的根证书,requests 看不到。
- 确认: 同一个地址 urllib 正常、requests 失败,或者服务器证书的签发者是公司内部 CA。
- 修复: 显式指定证书包:
import requests
requests.get("https://intranet.example.com", verify="/etc/ssl/certs/company-bundle.pem")
也可以安装 truststore(Python 3.10 及以上),在程序启动时调用 truststore.inject_into_ssl(),让 Python 直接使用操作系统的信任库。
4. 企业代理、上网行为管理或杀毒软件解密 HTTPS
这类设备和软件会用自己的根证书重新签名流量,openssl s_client 输出中的 issuer 会显示代理厂商或公司名称。
- 修复: 向 IT 部门要代理根证书(PEM 格式),追加到证书包副本里,再用
REQUESTS_CA_BUNDLE/SSL_CERT_FILE指向这个副本。
5. Python 3.13 及以上的严格校验
报错结尾是 Missing Authority Key Identifier 或提到其他证书扩展时,说明证书本身不符合 RFC 5280,被严格模式拒绝。按规范重新签发内部 CA 和站点证书。
6. 证书过期或系统时间不对
certificate has expired 说明证书链是可信的,只是日期不对。续期证书,或者校正客户端时间。
各环境修法
各环境的修法都是让 Python 用上一份包含所需根证书的 CA 证书包,区别在证书包的位置和更新方式。
macOS(python.org 安装包): 运行 Install Certificates.command,它会安装 certifi 并把它链接为该 Python 的 OpenSSL 证书包。
macOS(Homebrew 安装的 Python): 使用 Homebrew 的 OpenSSL 和证书包,brew upgrade 即可更新;私有根证书用 SSL_CERT_FILE 追加。
Windows: ssl.create_default_context() 会加载 Windows 的 ROOT 和 CA 证书库,所以 urllib 通常信任公司下发的根证书;requests 不会,需要 verify=、REQUESTS_CA_BUNDLE 或 truststore。
Debian / Ubuntu / Docker:
apt-get update && apt-get install -y ca-certificates
cp company-root.crt /usr/local/share/ca-certificates/ && update-ca-certificates
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
国内服务器 apt-get update 慢时,可以先把软件源换成阿里云、腾讯云或清华镜像,但 ca-certificates 必须安装。
CentOS / RHEL / 阿里云 Alibaba Cloud Linux: 根证书放进 /etc/pki/ca-trust/source/anchors/,执行 update-ca-trust,证书包路径是 /etc/pki/tls/certs/ca-bundle.crt。CentOS 7 这类老系统的证书包往往很久没更新,先 yum update ca-certificates。
pip 本身有单独的参数,见 pip SSL 证书验证失败。
不要这样做
关闭校验后报错会消失,Python 也不再验证服务器身份。
requests.get(url, verify=False)。 证书和域名校验全部关闭,链路上任何人都能冒充服务器,读到你的 token 和密码。ssl._create_default_https_context = ssl._create_unverified_context。 这段网上流传很广的代码会让整个进程都不校验证书,连你没写过的第三方库也一起受影响。- 用
urllib3.disable_warnings()屏蔽 InsecureRequestWarning。 警告没了,漏洞还在,以后更难排查。 - 为了让不规范的内部证书通过而全局关闭
VERIFY_X509_STRICT。 应该修证书,而不是放宽校验。
常见问题
为什么浏览器能打开,Python 却报证书验证失败?
浏览器会自动下载缺失的中间证书,并使用操作系统的信任库;Python 默认两样都不做:OpenSSL 需要服务器发完整证书链,requests 只信任 certifi。先用证书链补全工具检查证书链,链完整的话,差别就在信任库上。
pip install --upgrade certifi 能解决吗?
只有一种情况有用:服务器用的是较新的公开根证书,而你的 certifi 版本太旧。服务器缺中间证书、公司代理的根证书、内网私有 CA 这几种情况,升级 certifi 都没用,因为 certifi 只包含 Mozilla 收录的公开根证书。
urllib 和 requests 在这件事上有什么区别?
urllib、http.client 等标准库代码用 ssl.create_default_context(),读取 OpenSSL 的默认路径,在 Windows 上还会加载系统的 ROOT 和 CA 证书库。requests 默认用 certifi,除非你传了 verify= 或设置了 REQUESTS_CA_BUNDLE。所以同一个地址,可能 urllib 正常而 requests 报错,反过来也可能。
升级到 Python 3.13 后访问内网服务突然报错,为什么?
Python 3.13 在默认上下文里加了 VERIFY_X509_STRICT 和 VERIFY_X509_PARTIAL_CHAIN。严格模式会拒绝不符合 RFC 5280 的证书,例如缺少 Authority Key Identifier 扩展的证书,一些手工搭建的内部 CA 签出的证书就有这个问题。正确做法是按规范重新签发内部 CA 和证书。