通过代理使用 HTTPS 时如何处理 CERTIFICATE_VERIFY_FAILED 错误

目录
正确理解使用代理时的 CERTIFICATE_VERIFY_FAILED 错误
当应用无法按照当前策略验证 TLS 连接对端的证书时,就会出现 CERTIFICATE_VERIFY_FAILED 错误。通过代理访问 HTTPS 时,问题可能出在目标服务器、代理、受信任证书库或应用配置上。
正确的处理方式不是关闭证书检查,而是确定正在验证的连接对端、检查证书链,并提供适当的信任来源。这样既能保持传输加密,也能保留对服务器身份的验证能力。
代理如何影响 TLS 验证?
通过 CONNECT 转发隧道的代理
对于使用 CONNECT 的 HTTP 代理,代理会建立通往目标服务器的隧道。如果代理或其他中间设备没有进行 HTTPS 检查,TLS 会话就在应用与目标服务器之间建立;代理不会替换网站的证书。
在这种情况下,不应默认认为应用需要代理 CA 证书。错误可能源于服务器证书链缺少中间证书、CA 库过旧、系统时钟错误或主机名不匹配。
具有 HTTPS 检查功能的代理
在某些企业网络中,管理员会配置代理来检查 HTTPS 流量。该设备会终止来自应用的 TLS 连接,然后另行建立到目标服务器的 TLS 连接。
此时,应用收到的是由组织 CA 基础设施签发的证书。要验证成功,应用必须信任组织批准的 CA,同时证书仍须满足域名、有效期和信任链方面的要求。
到代理本身的连接也使用 TLS
如果应用支持通过 HTTPS 连接代理,则可能同时存在到代理的 TLS 层和到目标服务器的 TLS 层。这两层可能使用不同的 CA 配置。
因此,需要阅读错误信息,确定被拒绝的是哪张证书。为网站连接添加 CA,并不一定能解决代理本身的证书验证错误。
更改配置前先阅读错误信息
CERTIFICATE_VERIFY_FAILED 是一条通用错误信息。附带的描述通常有助于判断原因:
- 无法获取本地签发者证书:应用无法构建通往受信任 CA 的信任链;可能缺少根 CA 或中间证书。
- 证书链中存在自签名证书:证书链包含自签名证书,但尚未建立适当的信任关系。
- 证书已过期:证书链中的某张证书已过期,或客户端时间不准确。
- 主机名不匹配:请求的主机名与证书中记录的身份不符。
记录错误时,应保存应用名称、TLS 库版本、目标地址、代理类型和完整错误信息。不要将代理密码、令牌或身份验证信息写入共享日志。
确定使用代理时 SSL 错误的来源
在相同运行条件下进行比较
如果网络策略允许,请使用同一应用,在同一环境中,对同一域名分别进行直连和代理连接测试。如果直连正常而代理连接失败,则需要进一步检查对端提供的证书和代理配置。
浏览器能够打开网站,并不能证明应用已经具备所需的全部 CA。浏览器、Python、Java 和容器可能使用不同的信任库。
检查实际收到的证书
使用组织允许的 TLS 诊断工具,查看证书主体、签发者、有效期和证书链。对于支持 CONNECT 代理的 OpenSSL,可以使用如下形式的命令:openssl s_client -proxy proxy.example:8080 -connect service.example:443 -servername service.example -showcerts。
此示例适用于支持 CONNECT 的 HTTP 代理;代理身份验证能力取决于所用工具。输出中显示了证书,并不意味着连接一定有效,因为诊断工具可能在报告验证错误后继续运行。
如果签发者属于内部基础设施,请与管理部门核对。如果证书与预期配置不符,应在添加任何信任来源之前查明原因。
正确获取并验证代理 CA 证书
代理 CA 证书应通过官方管理渠道获取,例如设备管理系统、内部配置库或基础设施负责部门。不应下载来源不明的证书,并仅为使连接成功而将其安装。
使用前,请检查以下信息:
- 主体名称和签发者与组织文档一致。
- 证书仍在有效期内,且用途符合要求。
- 证书具备符合基础设施设计的 CA 属性。
- 已通过独立且可信的渠道核对证书指纹。
- 文件采用应用支持的格式,例如 PEM。
只需分发公开证书;不要索取或复制 CA 私钥。也不应默认将目标服务器证书放入 CA 库,以此代替修复信任链。
为相应应用配置正确的信任库
操作系统证书库
如果组织策略要求多个应用共同信任内部 CA,管理员可以将 CA 分发到操作系统信任库。在 Linux 上,具体流程取决于发行版;在 Windows 和 macOS 上,需要选择正确的用户范围或系统范围。
不过,某些运行时不会自动使用该证书库。安装后,仍须检查应用实际读取的是哪个 CA 来源。
应用专用 CA 证书包
当只有某个服务需要信任内部 CA 时,可以使用专用 CA 证书包。该文件应集中管理、设置适当的访问权限,并包含应用连接范围内所需的全部 CA。
如果库将自定义证书包视为替代信任来源,那么仅包含内部 CA 的文件可能导致连接公共网站失败。部署前需要确认该库的处理机制。
处理 Python 中的 CERTIFICATE_VERIFY_FAILED 错误
使用 Requests 的应用
对于 Requests,可以通过 verify 参数指定 CA 证书包路径,例如 requests.get(url, proxies=proxy_config, verify='/app/certs/company-ca-bundle.pem')。这种方式可以在保持证书验证开启的同时提供信任来源。
也可以使用环境变量 REQUESTS_CA_BUNDLE 指定证书包。需要确保进程确实接收到了该变量;服务环境、后台任务和终端会话的环境可能不同。
如果应用自行构建请求处理流程或使用自定义 Session,请检查环境配置的加载方式。不要认为只要在操作系统中设置了变量,所有代码都会使用它。
使用 ssl 或其他库的应用
使用标准库时,可以通过 ssl.create_default_context() 创建上下文,然后通过 load_verify_locations(cafile=...) 添加 CA。这种方式可以向已具有默认验证设置的上下文中加载额外的 CA。
某些库支持 SSL_CERT_FILE 或 SSL_CERT_DIR,但具体行为取决于运行时以及库初始化 TLS 的方式。应查阅相应文档,而不是将同一个变量套用于所有 Python 应用。
不应直接修改库安装目录中的 CA 文件:这些更改可能在更新时丢失,而且难以在不同环境之间保持受控和一致。
curl、Node.js 和 Java 的注意事项
- curl:--cacert 选项用于指定连接目标服务器时进行 TLS 验证所需的 CA。使用 HTTPS 代理时,如果当前构建版本支持,--proxy-cacert 用于验证到代理的 TLS 层。
- Node.js:NODE_EXTRA_CA_CERTS 可以在进程启动时添加 CA。直接传递给连接的 ca 配置可能改变所使用的 CA 来源。
- Java:需要检查 JVM 或应用实际使用的信任库。将 CA 安装到操作系统中,并不保证 Java 会自动识别该证书。
无论使用哪种工具,都应保留主机名和证书链检查。信任某个 CA,并不会使域名不匹配的证书变得有效。
容器和 CI/CD 环境需要单独配置
容器通常具有独立于容器宿主机的 CA 库。因此,仅在宿主机上安装证书还不够;镜像或部署配置必须为容器内部的进程提供 CA。
在 CI/CD 中,需要检查发生错误的具体步骤所使用的文件路径、读取权限、运行账户和环境变量。CA 在镜像构建步骤中存在,并不意味着它仍存在于最终运行的镜像中。
公开的 CA 证书不是秘密密钥,但其完整性非常重要。应管理证书来源、版本和更换计划,避免使用已过期或不再获批的 CA。
修改配置后的验证
更新信任来源后,请在之前报错的同一环境中重新执行请求。结果应证明连接是在仍然启用 TLS 验证的情况下成功建立的,而不仅仅是收到了 HTTP 响应。
- 确认证书与请求的域名匹配。
- 检查应用是否读取了正确的证书包或信任库。
- 根据获批的代理设计核对签发者。
- 测试所需的目标地址,以发现公共 CA 缺失的问题。
- 在获准的测试环境中,确认应用仍会拒绝无效证书。
如果错误仍然存在,请继续检查中间证书链、系统时钟、代理配置和实际使用的 CA 来源。不要批量添加未经验证的证书。
结论
处理通过代理使用 HTTPS 时的 CERTIFICATE_VERIFY_FAILED 错误,应先准确识别对应的 TLS 层以及验证失败的原因。可持续的解决方案是修复证书链、分发经过批准的 CA,并正确配置应用的信任库。
只要明确管理信任来源,并在实际环境中验证结果,应用就能通过代理正常运行,而无需牺牲 HTTPS 连接的身份验证能力。