编码代理与终端 · 收录于 2026-09-01 · 适用 1 工具 ✓ 实测验证

pip install 报 SSL 证书错误(CERTIFICATE_VERIFY_FAILED)——信任链断了,别急着 --trusted-host

已验证 2026-09-01pip 报 CERTIFICATE_VERIFY_FAILED——根因是 CA 信任链不含该证书(自签/内网私有 CA/公司代理 MITM);--trusted-host 只是临时跳过校验(实测 SSL 阶段通过),正规修=CA 导入系统信任库 + pip --cert 指向系统 bundle(实测通过,负对照成立)。

快速修复

# 方案 A(临时救急,先装起来):跳过单个主机的证书校验
pip install --trusted-host <镜像域名> --index-url <镜像URL> <包名>
# 持久化:pip config set global.trusted-host <镜像域名>

# 方案 B(正规修复,推荐):把 CA 导入系统信任库,pip 指向系统 bundle
sudo cp 你的CA证书.crt /usr/local/share/ca-certificates/pipssl.crt
sudo update-ca-certificates
pip config set global.cert /etc/ssl/certs/ca-certificates.crt
# 然后正常 pip install,不需要任何 --trusted-host
你的 CA 证书从哪来:内网镜像/自建仓库找管理员要(一般叫 ca.crt / rootCA.pem);公司代理找 IT 要拦截证书;浏览器能打开的话,可以在浏览器「证书查看器」里导出。

你会遇到什么

pip install 装包时突然报一长串:

ERROR: Exception:
HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/xxx/
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1081)'))

更迷惑的是:浏览器打开 PyPI 正常,curl 下载也正常,只有 pip 报证书错;或者换了个国内源(清华/阿里)还是同一个错。常见场景:公司内网自建 PyPI 镜像(Nexus/devpi)、公司/校园网 SSL 拦截代理、私有仓库用了自签证书、旧证书过期。

遇到它先别急着 --trusted-host 一把梭——那是跳过校验,等于把 HTTPS 的锁拆了,装了个「看起来成功」但可能被中间人替换的包。

本文路径:快速修复(30 秒一段命令)→ 一步步排查(可跳读)→ 原理与变体(症状/原因/证据)。赶时间直接跳到「快速修复」。

为什么会这样

pip 走 HTTPS 下载包时,Python 的 ssl 模块会做证书链校验:服务器证书必须由某个「被信任的 CA」签发,且链上每一张证书都完整、未过期、域名匹配。校验失败时 pip 宁可报错也不下载——这是安全设计,不是 bug。

「信任链断了」通常有三种形态:

  1. 自签证书:服务器用的是自己签的证书(内网镜像、私有仓库常见),不在任何 CA 信任库里 → self-signed certificate / unable to get local issuer certificate;
  2. 公司代理 MITM:公司网络用 SSL 拦截代理检查流量,代理给每个站点签发「代理自己的证书」——浏览器装了代理的 CA 所以正常,pip 用的是独立的证书库没装 → 换什么源都报同样的证书错(证书是代理签的,不是源的);
  3. 证书过期/系统时间错:报 certificate has expired。

而 --trusted-host 的作用是告诉 pip 别校验这个主机的证书——不是修复,是跳过。实测(见验证证据):加 --trusted-host 后 SSL 阶段直接放行。临时救急可以,长期用等于裸奔:HTTPS 的加密在,但「对方是不是真的 pypi.org」没人验证了。

一步步来

1. 确认报错形态 — 必做

pip install --index-url https://<你的源>/simple/ 某个包 2>&1 | tail -5
# 看到 CERTIFICATE_VERIFY_FAILED / self-signed / unable to get local issuer 就是本页问题

顺手用 curl 看一眼服务器证书长啥样(区分「自签」还是「私有 CA」):

echo | openssl s_client -connect <源域名>:443 -showcerts 2>/dev/null | grep -E "subject=|issuer=" | head -4
# subject=CN=xxx 与 issuer=CN=xxx 相同 = 自签;issuer 是公司内部 CA = MITM/私有 CA

2. 判断属于哪种场景 — 必做

  • 内网镜像/自建仓库 → 找管理员要 CA 证书(方案 B);
  • 公司/校园网代理拦截 → 找 IT 要代理的 CA 证书;换源没用,因为证书是代理签的,换哪个域名都一样报错;
  • 报 certificate has expired → 先 date 看系统时间对不对,再考虑证书过期;
  • 浏览器正常、pip 报错 → 正是「pip 信任库没装 CA」的典型特征,方案 B 直接对症。

3. 正规修复:CA 导入系统信任库 — 必做

sudo cp ca.crt /usr/local/share/ca-certificates/pipssl.crt
sudo update-ca-certificates   # 输出 "1 added" 即成功

4. 让 pip 用系统信任库 — 必做

pip config set global.cert /etc/ssl/certs/ca-certificates.crt
# 或环境变量:export PIP_CERT=/etc/ssl/certs/ca-certificates.crt
# 或单次:pip install --cert /etc/ssl/certs/ca-certificates.crt ...
为什么必须做这步:pip 自带独立的 CA 包(certifi),默认不读系统信任库。CA 只进系统库不够,还要让 pip 指向它(实测:只 update-ca-certificates 不带 --cert 依旧报错)。

5. 临时绕过只用于救急 — 可选

pip install --trusted-host <镜像域名> --index-url <镜像URL> <包名>

仅限「内网测试环境、马上要装」时用;装完尽快走方案 B,并在心里记一笔:跳过校验期间装的包,不保证来源可信。

怎么确认修好了

# 1. 不带任何 --trusted-host / --cert 也能装(用公网源或你的源都行)
pip install --index-url https://<你的源>/simple/ <包名>
# 2. 正向验证证书链真的被信任了
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt 你的证书.crt
# 输出 "OK" = 证书已被系统信任库接受

判定标准:不加跳过参数、不报 SSL 错误、装包成功,三条同时满足才算修好。

其它环境的表现

环境表现与处理
Python 3.14 + IP 字面量源(https://127.0.0.1:port)报 ValueError: check_hostname requires server_hostname(2026-09-01 实测):3.14 对 IP 字面量不发 SNI 导致 ssl 硬报错——源地址用域名(localhost 或内网域名),别用 IP
WindowsCA 导入:certutil -addstore Root ca.crt(受信任的根证书颁发机构);pip 指向:pip config set global.cert C:\path\ca-bundle.crt
macOS证书导入「钥匙串-系统」并信任;pip 同用 --cert 指向导出的 bundle
Docker 容器容器默认不带宿主 CA,构建时 COPY ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates;运行时同样要 pip config
换源仍报证书错大概率是代理 MITM:证书是代理签的,换域名没用,必须装代理的 CA(见「一步步来」第 2 步)
系统时间错乱报 certificate has expired:先 date 校时(sudo ntpdate / timedatectl),再谈证书

症状

pip install 报 SSLError(SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate) 或 unable to get local issuer certificate / certificate has expired;浏览器能打开包源、curl 正常,唯独 pip 失败;换源后错误不变。

原因

HTTPS 的证书校验链路是「客户端信任库 → 服务器证书 → 逐级验签直到根证书」:pip 用自己捆绑的 CA 包(certifi),与浏览器/系统信任库是三套独立列表——这就是「浏览器能开、curl 能下、pip 报错」的机制解释。校验有三个独立条件:①链上每张证书都在信任库可追溯(自签证书在链顶端直接断裂 → self-signed certificate);②证书未过期且系统时间正确(→ certificate has expired);③主机名与证书 SAN 匹配。公司代理 MITM 时,代理对每个站点签发的都是「代理自己的证书」,所以换源必然无效——证书不是源的,是代理的。确定性判断路径:openssl s_client -connect <host>:443 -showcerts 看 subject=/issuer= 是否相同(自签)或 issuer 是否为内部 CA(MITM);--trusted-host 是把「校验」整段短路,不是修复,装包前想清楚这一点。

pip 的 HTTPS 证书链校验失败:服务器证书不是由 pip 信任库(certifi)里任何 CA 签发。三种来源:自签证书(内网镜像/私有仓库)、公司 SSL 拦截代理的私有 CA(换源无效)、证书过期/系统时间错。--trusted-host 是跳过校验不是修复。

解法

  1. 正规修复:CA 证书导入系统信任库(/usr/local/share/ca-certificates/ + update-ca-certificates),再让 pip 指向系统 bundle(pip config set global.cert /etc/ssl/certs/ca-certificates.crt)——实测 SSL 阶段通过。
  2. 临时绕过:pip install --trusted-host <域名> ...,仅救急,长期等于关掉 HTTPS 身份校验。
  3. 场景分流:内网镜像找管理员要 CA;公司代理找 IT 要拦截证书(换源无用);expired 先校时。
  4. Python 3.14 注意:源地址用域名不用 IP 字面量(实测会触发 check_hostname requires server_hostname)。
验证证据
  • 2026-09-01 本机实测(WSL Ubuntu 26.04,Python 3.14.4,pip 25.1.1,自建 127.0.0.1:18443 HTTPS 源 + 自签 CA,SAN=DNS:localhost,IP:127.0.0.1):实测复现日志(2026-09-01)
  • 基线:pip install --index-url https://localhost:18443/simple/ six → [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1081)(实锤);
  • 快速绕过:--trusted-host localhost → SSL 阶段放行,后续报 No matching distribution(实锤:跳过校验生效);
  • 正规修复:CA 复制进 /usr/local/share/ca-certificates/ + update-ca-certificates + pip --cert /etc/ssl/certs/ca-certificates.crt → SSL 阶段放行(实锤);
  • 负对照:撤销 CA + 不带 --cert → 回到 CERTIFICATE_VERIFY_FAILED: self-signed certificate(实锤:修复确实依赖信任库,不是缓存);
  • 变体:https://127.0.0.1:18443(IP 字面量)→ ValueError: check_hostname requires server_hostname(Python 3.14 新行为,实锤)。

相关 canonical

适用工具 (1)

本问题方案已挂到下列工具的方案页——从工具页也能回到这里,双向可达。

pip · 外部工具

来源与证据

收录 验证 ✓ 实测验证适用 1 工具
方案含本地实测或来源可复核的解法;未实测项已在文中如实标注。

← 编码代理与终端 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡