编程语言

CERT_HAS_EXPIRED:Node.js 报 certificate has expired

作者: · 最后核实

报错信息Error: certificate has expired (code: 'CERT_HAS_EXPIRED')

certificate has expired(CERT_HAS_EXPIRED)的意思是:按客户端的时钟判断,Node.js 构建的证书链里有一张证书已经过了到期时间。 通常是服务器证书本身没续期;有时是服务器还在发送一张过期的中间证书,或者客户端的日期不对。

快速修复

多数情况是服务器证书没续期,续期并重载 Web 服务器即可。先查看服务器证书的日期和本机时间:

date -u
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -startdate -enddate
  • notAfter 已经过去: 续期证书,连同完整证书链一起部署,然后重载 Web 服务器。
  • 站点证书没过期: 用 -showcerts 检查服务器发送的每一张证书,把过期的中间证书从证书包里去掉。
  • 本机时间不对: 修复客户端时间同步(systemd 的 Linux 上执行 timedatectl set-ntp true)。

原因

最常见的是服务器证书没续期,其次是证书包里有过期的中间证书,再次是客户端时钟错误。

1. 服务器证书过期

  • 确认: 上面的命令显示 notAfter 早于现在;SSL 检测工具提示已过期。
  • 修复: 续期。ACME 客户端执行 certbot renew 或 acme.sh --renew -d example.com,并确认有部署钩子重载 Nginx 或 Apache。续期“成功”了但还是旧证书,说明 Web 服务器没重载,或者引用的是另一份副本。阿里云、腾讯云的免费证书到期后需要重新申请并重新部署。

2. 证书包里有过期的中间证书或旧交叉签名

有些服务器在旧链路的中间证书或交叉签名证书过期后,仍然把它们发给客户端。路径构建方式不同的客户端就会失败。

  • 确认: -showcerts 列出的除站点证书外,还有一张 notAfter 已过期。
  • 修复: 按 CA 当前的中间证书重新拼接证书链(可以用证书链补全工具),去掉过期的那张。

3. 客户端时钟错误

宿主机没开时间同步的容器、从快照恢复的虚拟机、嵌入式设备,日期经常是错的。

  • 确认: date -u 和真实时间差很多;访问其他 HTTPS 网站也报同样的错。
  • 修复: 在宿主机开启 NTP(chrony、systemd-timesyncd),国内可以用阿里云、腾讯云提供的 NTP 服务器;容器使用宿主机时钟。

4. Node.js 根证书过旧或镜像地址过时

很老的 Node.js 版本内置的根证书列表里还有、或者依赖已过期的根证书。已停用的旧镜像域名、旧 API 域名也可能出示过期证书。

  • 修复: 升级到仍在维护的 Node.js LTS 版本,更新配置里过时的地址(npm 用 npm config get registry 检查)。

各环境修法

防止再次过期靠三件事:自动续期、续期后自动重载、手动上传的证书改用托管证书。

Certbot: sudo certbot renew --deploy-hook "systemctl reload nginx";用 systemctl list-timers | grep certbot 确认定时器在运行。

宝塔面板: 在站点 SSL 页面确认“自动续签”已开启,并查看续签日志;续签失败常见原因是 80 端口不通或域名解析变了。

Kubernetes: 检查 TLS Secret 里的证书和 cert-manager 状态,见 cert-manager 证书一直不 Ready。

云负载均衡和 CDN: 手动上传的证书不会自己续期,改用云厂商的托管证书,或者用脚本自动上传。

无法升级的老客户端: 老设备或老运行时验证不了当前证书链时,只能升级,没有安全地“接受过期证书”的办法。

不要这样做

关闭校验、回拨时钟和置之不理,都会掩盖问题或把问题扩大到别处。

  • 用 NODE_TLS_REJECT_UNAUTHORIZED=0 绕过过期证书。 它关掉的是全部校验,不只是日期检查。
  • 把系统时间往回调。 其他证书、日志、令牌校验都会跟着出问题。
  • 因为“只有 Node.js 报错”就不管。 手机 App、合作方的接口调用等其他非浏览器客户端同样在失败。

常见问题

浏览器显示证书正常,Node.js 却说过期了,为什么?

浏览器可能构建出不同的证书链,比如绕开服务器仍在发送的过期交叉签名中间证书,而且它用的是自己最新的根证书库。Node.js 也可能跑在时钟错误的机器上。用 openssl s_client -showcerts 看服务器发出的链,和浏览器显示的对比。

npm install 报 CERT_HAS_EXPIRED,是镜像源的问题吗?

很可能。旧的淘宝 npm 镜像域名 registry.npm.taobao.org 已停用,它的证书在 2024 年 1 月过期,还在用这个地址的项目会报这个错。执行 npm config get registry 检查,改成 https://registry.npmmirror.com,同时检查项目里的 .npmrc 和锁文件里的地址。

怎么知道到底是哪张证书过期了?

打印服务器发出的每一张证书的日期:openssl s_client -connect host:443 -servername host -showcerts </dev/null,对每一段执行 openssl x509 -noout -subject -enddate。SSL 检测工具也会列出链中每张证书的到期时间。

怎么避免以后再出现?

用 ACME 客户端或云厂商的托管证书自动续期,续期后自动重载服务器,并从外部监控到期时间。2026 年证书最长 200 天,2027 年降到 100 天,2029 年降到 47 天,手动续期跟不上。

资料来源

  1. Node.js TLS (SSL) documentation
  2. OpenSSL X509_STORE_CTX_get_error (verification error codes)
  3. RFC 5280: Internet X.509 PKI Certificate and CRL Profile