编程语言
OpenSSL::SSL::SSLError certificate verify failed(Ruby)
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)OpenSSL::SSL::SSLError ... certificate verify failed 的意思是:Ruby 的 OpenSSL 连上了服务器,但没能用它加载的 CA 证书验证服务器的证书链。 通常是 Ruby 去找的 CA 文件不存在或者过旧,或者对方服务器没有发送中间证书。
快速修复
多数情况让 Ruby 用上一个有效的 CA 证书包就能修好。先看你的 Ruby 用的是哪个 OpenSSL、去哪里找 CA 证书:
ruby -ropenssl -e 'puts OpenSSL::OPENSSL_LIBRARY_VERSION, OpenSSL::X509::DEFAULT_CERT_FILE, OpenSSL::X509::DEFAULT_CERT_DIR'
ls -l "$(ruby -ropenssl -e 'print OpenSSL::X509::DEFAULT_CERT_FILE')"
文件不存在时,让 Ruby 使用一个有效的证书包:
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt # Debian/Ubuntu 路径
gem 和 bundle 报错时,运行 bundle doctor ssl(较新的 Bundler)和 gem update --system。如果只有某一个域名报错,用 SSL 检测工具检查它的证书链。
原因
Ruby 找不到 CA 文件或 CA 过旧最常见,其次是对方服务器没发中间证书,下面共列出 5 种原因。
1. Ruby 的 OpenSSL 找不到 CA 文件
用 rbenv/ruby-build、asdf、rvm 编译的 Ruby 会链接某个特定的 OpenSSL(macOS 上通常是 Homebrew 的)。这个 OpenSSL 没有证书文件,或者升级时被删掉了,所有校验都会失败。
- 确认:
DEFAULT_CERT_FILE指向的路径不存在或是空文件。 - 修复: 安装或更新该 OpenSSL 对应的 CA 证书包,设置
SSL_CERT_FILE,或者重新安装这个 Ruby 版本,让它链接到当前的 OpenSSL。
2. CA 证书或 RubyGems 版本过旧
老系统和旧版 RubyGems 缺少较新的根证书。
- 确认: 报错的网站所用根证书比你的系统或 RubyGems 版本更新。
- 修复: 更新系统
ca-certificates包,执行gem update --system和bundle update --bundler。CentOS 7 这类已停止维护的系统,证书包和 OpenSSL 都很旧,长期看应升级操作系统。
3. 系统时间不对
RubyGems 指南把系统时间错误列为两大原因之一:时间偏到过去或未来,有效证书也会显示为已过期或尚未生效。
- 修复: 校正时间并开启 NTP 自动同步。国内服务器可以用阿里云、腾讯云提供的 NTP 服务器。
4. 对方服务器证书链不完整
- 确认:
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts只列出一张证书。 - 修复: 需要对方服务器发送中间证书,见缺少中间证书和证书链补全工具。
5. 公司代理或内网 CA
不在公开证书包里的根证书需要显式添加:
require "net/http"
require "openssl"
store = OpenSSL::X509::Store.new
store.set_default_paths
store.add_file("/etc/ssl/certs/company-root.pem")
http = Net::HTTP.new("intranet.example.com", 443)
http.use_ssl = true
http.cert_store = store
puts http.get("/").code
如果整台机器都要信任,就把根证书加进系统信任库。报错文字是 self-signed certificate in certificate chain 时,见那一页。
各环境修法
不管哪个平台,修法都是更新 OpenSSL 和 CA 证书包,让 Ruby 读到它;私有根证书加进系统信任库或用 SSL_CERT_FILE 指定。
macOS + Homebrew + rbenv/asdf: 执行 brew upgrade openssl@3 ca-certificates,再用 ruby-build 重新安装受影响的 Ruby 版本,让它链接当前的 OpenSSL。临时办法是 export SSL_CERT_FILE="$(brew --prefix)/etc/ca-certificates/cert.pem"。
Debian / Ubuntu:
sudo apt-get install --reinstall ca-certificates
sudo cp company-root.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates
CentOS / RHEL / Alibaba Cloud Linux: 根证书放进 /etc/pki/ca-trust/source/anchors/,再执行 update-ca-trust。
Windows(RubyInstaller): 用上面的命令看 Ruby 去哪里找证书,再用 SSL_CERT_FILE 指向一个包含公司根证书的证书包。
Docker: 镜像里安装 ca-certificates,并在 bundle install 之前把私有根证书复制进去。
国内镜像源: 使用 Ruby China 镜像时,地址保持 https://:bundle config mirror.https://rubygems.org https://gems.ruby-china.com。换源解决的是速度问题,证书错误仍要按上面的方法处理。
Faraday / HTTParty: 都可以指定 CA 文件或证书库(Faraday 用 ssl: { ca_file: ... },HTTParty 用 ssl_ca_file:)。
不要这样做
关闭校验能让报错消失,但任何人伪造的证书都会被接受。
http.verify_mode = OpenSSL::SSL::VERIFY_NONE。 接受任何人的任何证书,账号密码和接口数据都可能被截获。- 重定义
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE。 这段老代码会让进程里所有库都不再校验证书。 - 在
.gemrc里写:ssl_verify_mode: 0,或把 gem 源改成http://。 gem 是你要执行的代码,不经认证地下载等于给篡改敞开大门。
常见问题
gem install 或 bundle install 报 certificate verify failed,先做什么?
Bundler 支持的话先运行 bundle doctor ssl,然后按 RubyGems 指南依次处理:升级 RubyGems 和 Bundler(gem update --system)、检查系统时间、更新系统 CA 证书。访问 rubygems.org 或 gems.ruby-china.com 失败,大多数靠这三步就能解决。
同一台机器上 curl 正常,Ruby 却报错,为什么?
Ruby 用的是编译时链接的那个 OpenSSL,它和 curl 命令可能不是同一份,默认 CA 路径也不同。对比 ruby -ropenssl -e 'puts OpenSSL::X509::DEFAULT_CERT_FILE' 和 curl -v 输出里的 CAfile:,Ruby 的文件不存在时设置 SSL_CERT_FILE。
升级 Homebrew 或 macOS 之后才开始报错,怎么回事?
ruby-build、rvm 编译的 Ruby 会链接某一个具体的 OpenSSL 安装。这个 OpenSSL 或它的证书文件被移动、删除后,Ruby 就加载不到根证书了。重新安装该 Ruby 版本(让它链接当前的 OpenSSL),或者用 SSL_CERT_FILE 指向有效的证书包。
只想让某一个 HTTP 客户端信任公司根证书,怎么写?
建一个包含默认根证书和公司根证书的证书库传给客户端:store = OpenSSL::X509::Store.new; store.set_default_paths; store.add_file("/path/company-root.pem"),Net::HTTP 用 http.cert_store = store,Faraday 用 ssl: { cert_store: store }。