错误信息
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
也可能输出为
- ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Python 会附加 C 源代码位置,例如“(_ssl.c:1082)”;这个数字随 Python 版本变化,对诊断没有意义。urllib 会把错误包装成“<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] …>”,requests 和 urllib3 则包装成“Max retries exceeded … (Caused by SSLError(…))”。
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)
含义
Python 的 ssl 模块要求 OpenSSL 验证服务器证书,而 OpenSSL 在这个 SSLContext 加载的 CA 证书中,找不到证书链中最后一张证书的颁发者。服务器不一定配置有误:常见的情况是,这个 Python 使用的 CA 证书包与你的浏览器或操作系统使用的不同(甚至是空的)。
原因
macOS 上从 python.org 安装的 Python 缺少证书
常见python.org 的 macOS 安装程序自带 OpenSSL,它不读取 macOS 钥匙串。在为该 Python 版本运行“Install Certificates.command”之前,它根本没有任何 CA 证书,因此每个 HTTPS 请求都会报这个错误。
私有 CA 或执行 TLS 检查的代理
常见公司内部服务和企业代理使用的证书,其根证书是通过操作系统的证书存储分发到受管理机器上的。Python 的 OpenSSL 不会查询 Windows 或 macOS 的证书存储,而 Linux 容器中除非专门添加,否则根本没有这个根证书。
服务器没有发送中间证书
常见OpenSSL 不会获取缺失的中间证书。如果服务器只发送叶子证书,Python 的验证就会失败,而会缓存和下载中间证书的浏览器看起来一切正常。
CA 已添加到系统证书存储,但库使用的是 certifi
偶见requests、httpx、aiohttp、botocore 和 pip(使用旧版证书处理方式时)都使用 certifi 证书包进行验证,这个证书包以 Python 包的形式分发。用 update-ca-certificates 安装的根证书或安装到操作系统钥匙串中的根证书,对它们来说是不可见的。
SSL_CERT_FILE 或 SSL_CERT_DIR 指向了错误的位置
罕见这些变量会替换 OpenSSL 的默认位置。其他工具遗留的路径,或者只包含私有根证书的证书包,会让公共网站验证失败(或者反过来)。
修复方法
- 1.
macOS 上的 python.org 版本:运行 Install Certificates.command
该脚本会安装 certifi,并将其链接为该 Python 所用 OpenSSL 的证书包。安装其他 Python 版本后,必须重新运行它。
bash # macOS, python.org installer: run once per installed Python version "/Applications/Python 3.14/Install Certificates.command" - 2.
通过 truststore 使用操作系统的证书存储
truststore 让 ssl.SSLContext 通过操作系统进行验证(macOS 上的钥匙串、Windows 证书存储、Linux 上的系统证书包),这样 IT 部门部署的根证书在 Python 中也能生效。它同样适用于 urllib3、requests 和 pip,而 pip 从 24.2 版本起默认就使用它。
python # pip install truststore==0.10.5 — verify with the OS trust store (Keychain, Windows store, Linux bundle) import truststore truststore.inject_into_ssl() # call once at start-up, before creating connections import urllib.request urllib.request.urlopen("https://api.example.com/") - 3.
为单个客户端显式加载 CA
对于与私有服务通信的代码,把 cafile(或 cadata)传给 ssl.create_default_context()。该上下文仍会验证主机名和日期。
python import ssl, urllib.request ctx = ssl.create_default_context(cafile="/etc/ssl/private-ca/corp-root-ca.pem") urllib.request.urlopen("https://api.example.com/", context=ctx) - 4.
检查并设置默认的证书包路径
ssl.get_default_verify_paths() 会显示 OpenSSL 使用的文件和目录。SSL_CERT_FILE 和 SSL_CERT_DIR 会为当前进程覆盖它们;把它们指向同时包含公共根证书和你的 CA 的证书包。
bash # Where does this Python look by default? python3 -c "import ssl; print(ssl.get_default_verify_paths())" # Point OpenSSL-based clients at a bundle that includes your CA export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt - 5.
服务器所有者:发送完整的证书链
如果握手中只有一张证书,而它的颁发者是公共中间证书,就为服务器配置完整证书链文件。
bash # Server side: 1 certificate in the handshake = intermediate missing openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -c 'BEGIN CERTIFICATE'
相关错误
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unab…Python urllib.requestMax retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1…requests / urllib3 (Python)There was a problem confirming the ssl certificate: [SSL: CERTIFICATE_VERIFY_FA…pip[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)