☰
npm install 报 CERT_HAS_EXPIRED?一文讲清 TLS 证书校验原理与排查方案
2026/10/1 13:10:13 网站建设 项目流程

这两天接手一个老项目,同事在拉依赖的时候终端直接被一连串npm ERR! code CERT_HAS_EXPIRED刷屏,第一反应是“registry 挂了?”,结果源站好好的,npm 自己的证书检查却死活过不去。这个报错在 Node 生态里不算冷门,尤其是这两年代理、镜像、企业内网环境越来越复杂之后,出现的频率明显比前几年高。

这篇文章就围绕CERT_HAS_EXPIRED这个错误码,把它背后的成因、排查顺序、修复方案和坑位一次性说清楚。适合用 npm 做依赖管理的前端工程师、Node 后端开发者,以及所有在公司内网环境下装包、跑npm install、npm run build的同学参考。我会尽量用实际场景来讲,而不是贴一段官方文档就完事。

1. CERT_HAS_EXPIRED 到底在说什么

1.1 先看懂报错本身

npm install的时候如果证书校验失败,终端通常会打出类似这样的信息:

npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npmjs.org/xxx failed, reason: certificate has expired

注意看最后一行,里面的 URL 可能是官方源,也可能是你配置的镜像源,但核心都是一样的:Node 在发起 HTTPS 请求时,对服务端返回的证书做完整性校验,发现“当前时间已经超过了证书的有效期”,于是直接中断了 TLS 握手。

这个错误码的含义很直接——不是包下载不了,而是 TLS 这一层压根就没通过。很多新手会误以为是网络断了、源挂了或者需要清缓存,其实都不沾边。它属于 Node.js 内置tls模块抛出的证书校验错误,跟 npm 本身的关系不大,哪怕你换成 pnpm 或者 yarn,只要底层还是 Node 在发 HTTPS 请求,一样会碰到同款问题。

我在实际排查中还发现,很多人会把CERT_HAS_EXPIRED和另外几个错误码搞混:

错误码含义常见场景
CERT_HAS_EXPIRED证书已过期系统时间错误、镜像站证书确实过期
UNABLE_TO_VERIFY_LEAF_SIGNATURE无法验证叶子证书签名自签名证书、证书链不完整
SELF_SIGNED_CERT_IN_CHAIN证书链里出现自签名证书公司代理做了 HTTPS 解密替换
DEPTH_ZERO_SELF_SIGNED_CERT服务端直接返回自签名证书内网私有源,未配置信任
ERR_TLS_CERT_ALTNAME_INVALID证书域名不匹配hosts 配错、代理规则串了

这几个错误在视觉上都是红字一大片,但如果能准确区分,排查方向会完全不同。CERT_HAS_EXPIRED的关键词就是“过期”,所以第一反应应该是时间,其次是证书本身真的过期了。

1.2 TLS 证书校验的简化原理

要理解这个问题,得先知道 Node 在访问 HTTPS 地址时做了什么。打个比方:你去机场安检,掏出身份证,工作人员要先看两件事——证件是不是真的、证件在不在有效期内。CERT_HAS_EXPIRED就相当于“证件本身是真的,但已经过期了”。

具体到技术层面,HTTPS 建立连接时会经历 TLS 握手,客户端会拿到服务端的证书,然后做三件事:

  1. 检查证书的有效期(notBefore到notAfter),看当前时间是否落在区间内。
  2. 检查证书的签发链,逐级回溯到受信任的根证书。
  3. 检查证书里的域名是否和请求地址匹配。

CERT_HAS_EXPIRED就是卡在第一步——有效期检查没过。这里有一个容易被忽略的点:证书校验用的“当前时间”不是互联网上的标准时间,而是你本机的系统时间。如果你的电脑时间跑偏了,哪怕服务端证书完全正常,同样会报过期。

反过来还有一种情况:系统时间调得太快,跑到了证书生效日期之前,Node 也可能报CERT_NOT_YET_VALID,但这个错误在新版 Node 里有时候也会被归类到证书校验失败的大类里。所以遇到证书相关的报错,第一步永远是看时间,这一步正确率极高。

2. 动手前按顺序排查,别一上来就改配置

2.1 系统时间:十次报错里至少有六次是它

我的习惯是:看到CERT_HAS_EXPIRED,什么都不动,先看本机时间。

# Windows 命令行 date /t time /t # macOS / Linux date -R # Linux 更推荐 timedatectl status

你会发现几种典型情况:

  • 系统时间停在几个月甚至几年前,比如 CMOS 电池没电了。
  • 时间被改到了未来,比如虚拟机快照回滚之后时间错乱。
  • 双系统电脑,Windows 和 Linux 各写各的硬件时钟,来回切换几次时间就乱了。
  • Docker 容器或 CI 机器用的基础镜像时间不同步。

这里有个细节:如果只是差几分钟,通常不会触发CERT_HAS_EXPIRED,因为证书有效期一般有充足的余量,但也会引发其他怪问题,比如git commit时间错乱、JWT 校验失败。真正让 Node 报证书过期的,往往是时间差了好几个月甚至好几年。所以一旦看到这个错误码,直接在终端跑一下date,一眼就能判断是不是时间的锅。

为什么会这样?因为证书里的notBefore和notAfter是绝对时间,本机时间如果跑到有效期之外,TLS 校验直接失败。这是设计上的安全机制,防止旧证书被无限期复用,但也意味着“时间不准”会连累所有 HTTPS 请求。

2.2 镜像源与代理:企业网络的高频雷区

排除了系统时间之后,第二步看 npm 到底在连哪个源。

npm config get registry

正常情况下你会看到https://registry.npmjs.org/,国内很多同学会配置成https://registry.npmmirror.com/(以前的淘宝镜像)。如果用的是某个第三方维护的源,而这个源的证书恰好过期了,那就必然报CERT_HAS_EXPIRED。这种情况在 2022 年前后特别常见,因为老的淘宝镜像域名registry.npm.taobao.org停止维护之后,很多人还在沿用旧配置,域名证书一变,报错就来了。

还有一个在企业里很常见的场景:公司网络做了 HTTPS 拦截(SSL 解密),所有 HTTPS 请求都被网关替换成了公司内部的证书。如果公司内部 CA 的证书更新不及时,Node 的证书校验就会失败。这时候报错的可能是CERT_HAS_EXPIRED,也可能是SELF_SIGNED_CERT_IN_CHAIN,取决于内网证书的具体状态。

判断方法很简单:浏览器打开你配置的 registry 地址,点地址栏的小锁,看一下证书有效期和签发机构。如果浏览器也提示证书过期,那基本就是源站或代理的问题,不是 npm 的问题。

2.3 版本问题:老 Node 和老 npm 的兼容性隐患

第三个排查方向是 Node 和 npm 本身的版本。

node -v npm -v

这里有一个历史教训值得说。2021 年 9 月底,Let's Encrypt 的旧根证书 DST Root CA X3 到期,而它的新根证书 ISRG Root X1 此前主要通过交叉签名来获得信任。过期之后,一些比较老的系统、老版本的 Node.js(尤其是内置根证书列表没更新的版本),在访问使用 Let's Encrypt 证书的站点时就开始报证书链错误,表现形式和CERT_HAS_EXPIRED很接近。

这类问题用“改时间”“换源”都解决不了,唯一干净的办法是升级 Node.js 到维护版本,让它带上新的根证书库。所以,如果你的 Node 版本特别老(比如 8.x、10.x、12.x 的早期版本),别犹豫,直接升级。

3. 实操修复方案,按优先级排列

3.1 方案 A:校准系统时间(优先级最高)

如果确认是本机时间不对,修起来很简单。

Windows 用户:

  • 打开“设置 -> 时间和语言 -> 日期和时间”,打开“自动设置时间”,再点一下“立即同步”。
  • 命令行方式:
w32tm /resync

macOS 用户:

  • 打开“系统设置 -> 日期与时间”,打开“自动设置日期与时间”。
  • 命令行方式:
sudo systemsetup -setusingnetworktime on

Linux 用户:

sudo timedatectl set-ntp true timedatectl status

看到System clock synchronized: yes就说明时间已同步。

这里有一个双系统用户经常踩的坑:Windows 默认把硬件时钟当本地时间,Linux 默认把硬件时钟当 UTC,两个系统各写各的,来回切换几次,时间必然乱。解决办法是让 Windows 也使用 UTC 作为硬件时钟,或者让 Linux 把 RTC 设置为本地时间。我自己的做法是在 Windows 注册表里加一个RealTimeIsUniversal的 DWORD 值设为 1,这样两个系统就统一了,之后再没出过切换系统时间跳变的问题。

时间校准之后,不用改任何 npm 配置,重新执行安装命令:

npm install

绝大多数时候到这里就已经好了。

3.2 方案 B:切换或更新 npm 镜像源

如果时间没问题,但 registry 指向的源确实证书失效,那就换源。

先看当前用的什么源:

npm config get registry

如果你看到的是已经废弃的老地址https://registry.npm.taobao.org,应该立刻切换到维护中的镜像:

npm config set registry https://registry.npmmirror.com

如果你在海外或者专线网络环境下访问官方源速度还行,也可以切回官方:

npm config set registry https://registry.npmjs.org/

切换之后检查一下是否生效:

npm config get registry npm ping

npm ping会实际请求一次 registry 并返回状态。看到PING和正常响应之后,再执行安装就不会报证书错了。

这里多说一句:换源只是把“请求的地址”换了,如果你本机时间有问题,换哪个源都一样报错,所以顺序一定不要反——先时间,后源。另外有些同学喜欢用nrm这类工具管理源,也可以,但原理是一样的,本质就是改registry配置。

3.3 方案 C:升级 Node.js 与 npm

如果你的 Node 版本太老,或者 npm 版本存在已知的 TLS 兼容问题,升级是最省事的解法。

先升级 npm:

npm install -g npm@latest

但这里有个现实问题:npm 本身如果已经因为证书错误跑不动了,那npm install -g大概率也会失败。所以更可靠的方式是直接升级 Node.js,npm 会随附升级。

  • 去 Node.js 官网下载 LTS 版本安装包,覆盖安装。
  • 或者用版本管理工具,macOS/Linux 用nvm,Windows 用nvm-windows:
# nvm(macOS / Linux) nvm install --lts nvm use --lts # nvm-windows(Windows) nvm install 20.19.0 nvm use 20.19.0

为什么我强调用 LTS?因为 Current 版本虽然新,但某些生态兼容性反而不如 LTS 稳定,项目里装的依赖不一定跟得上。对绝大多数业务项目来说,选当前活跃维护的 LTS 版本是性价比最高的选择。升级完再跑node -v和npm -v确认一下,然后重新安装依赖。

3.4 方案 D:临时绕过与最终兜底

如果以上方案都试过了,时间是对的、源是对的、版本也是新的,但就是报证书错误,可能是你处在某种特殊网络环境里,需要临时绕过证书校验来确认问题。

npm config set strict-ssl false

这条命令会关闭 npm 的 SSL 严格校验,之后再npm install就不会再检查证书了。但我要非常严肃地说:这只是排查手段,不是解决方案,用完必须改回来。

npm config set strict-ssl false # 临时 npm install npm config set strict-ssl true # 用完立刻改回

为什么不能长期开着?strict-ssl false等于告诉 Node:只要门卫喊我名字就放行,至于身份证真假不重要。这在公网环境下极其危险——你下载的依赖包内容有可能被中间人篡改,轻则引入恶意代码,重则整个开发机甚至生产环境沦陷。我见过有同事为了图省事把strict-ssl false写进全局配置,几个月后才发现所有依赖的来源都没法追溯,这种沉默成本比当时那点排查时间高太多了。

比关闭校验更稳妥的做法是“让 Node 信任你该信任的证书”。如果你在公司内网,网关替换了证书,正确做法是拿到内网 CA 证书,然后用环境变量指定额外信任的 CA:

# Windows PowerShell $env:NODE_EXTRA_CA_CERTS="C:\path\to\company-ca.crt" # macOS / Linux export NODE_EXTRA_CA_CERTS=/path/to/company-ca.crt

或者写进 npm 配置:

npm config set cafile /path/to/company-ca.crt

这样 Node 会在原有根证书之外,额外信任你指定的 CA,既能通过校验,又不用关闭安全检查。对内网环境来说,这才是正规解法。

4. 常见问题速查与避坑记录

4.1 常见报错场景速查表

我把自己遇到过的、以及帮别人排查过的典型场景整理成了一张表,可以直接照着对:

报错/现象常见原因快速处理
CERT_HAS_EXPIRED,本机时间差几个月系统时钟错误校准时间,开启 NTP 自动同步
CERT_HAS_EXPIRED,registry 是旧镜像域名镜像站证书过期/域名废弃更换registry.npmmirror.com
CERT_HAS_EXPIRED,公司内网必现网关 SSL 拦截,内网 CA 过期更新内网 CA 证书,用NODE_EXTRA_CA_CERTS指定
UNABLE_TO_VERIFY_LEAF_SIGNATURE证书链不完整/自签名检查代理配置,或指定cafile
SELF_SIGNED_CERT_IN_CHAIN代理替换了证书但未被信任添加内网 CA 到信任列表
2021 年后老 Node 突然报证书错旧根证书到期升级 Node.js 到维护版 LTS
ERR_TLS_CERT_ALTNAME_INVALID域名不匹配检查 hosts、代理规则、镜像源地址

这张表不是万能的,但覆盖了绝大多数CERT_HAS_EXPIRED的真实场景。如果你遇到的报错不在表里,大概率是网络拓扑比较特殊,建议把完整报错贴出来,重点看request to后面的 URL 是哪个域,以及reason部分的原文。

4.2 我踩过的几个坑

聊几个真实案例,都是那种“看文档根本不会告诉你”的细节。

第一个坑:公司 WiFi 的强制代理。

有段时间我换了工位,连了新的办公 WiFi,结果npm install开始报CERT_HAS_EXPIRED。查了一圈,发现是公司网关对 HTTP 流量做了透明代理,所有 HTTPS 请求都被替换证书。之前我在宿舍用的家用网络没这回事,所以一直没暴露。后来排查到是网关的证书过期,IT 部门更新后就好了。这种场景最容易让人误判成 npm 配置问题,浪费很长时间。

第二个坑:虚拟机快照回滚导致时间穿越。

我习惯在虚拟机里做多版本 Node 的测试,有一次为了方便,直接回滚了快照,结果虚拟机的系统时间回到了两个月前。当时没注意,进去就跑npm install,报错。我还以为是镜像源的问题,换了三个源都没用,最后随手敲了个date才发现时间穿越了。从那以后,我每次回滚快照或者恢复虚拟机,第一件事就是看一眼系统时间。

第三个坑:缓存里的脏数据。

证书错误本身不是缓存问题,但如果你之前用--strict-ssl=false装过一部分包,缓存里可能混入了一些不完整的元数据。后续即使证书问题修好了,偶尔也会出现校验不一致的怪报错。这时候可以清一下 npm 缓存再重试:

npm cache clean --force

注意:npm cache clean不是万金油,它解决不了证书错误本身,只是在“配置已修正但仍有诡异现象”的时候补一刀。我习惯把它放在最后一步,而不是第一步。

第四个坑:npm命令本身都跑不起来。

有些同学会遇到npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称,然后误以为这也是证书问题。其实这是完全不同的两个问题——npm不在 PATH 环境变量里。这属于 Node.js 安装时的环境变量配置问题,跟CERT_HAS_EXPIRED没有关系。排查证书问题之前,先确认npm -v能跑,能跑再继续看证书,不要混为一谈。

4.3 预防措施

证书过期这种事,预防比修复更重要。我有几个习惯,分享出来供参考:

  • 系统时间必须开自动同步。不管是个人电脑还是 CI 机器,确保 NTP 时间同步服务处于开启状态。Linux 上用systemd-timesyncd或chrony,Windows 上用系统自带的“自动设置时间”。
  • 每季度检查一次 registry 配置。一条命令的事,npm config get registry,顺手确认有没有被装了什么奇奇怪怪的源。
  • Node 版本保持在一个维护中的 LTS 上。不用追新,但也不能停在 EOL 版本上。老版本带来的不只是证书问题,还有各种各样的安全隐患。
  • 公司内部自建 registry 和 CA 的话,给证书设置到期提醒。提前一个月做证书轮换,不要等到开发者集体报错了才处理。
  • CI 脚本里加一步环境信息打印。在跑npm install之前先输出node -v、npm -v和date,出问题的时候日志里什么都有,排查效率翻倍。

5. 最后说点个人体会

我见过太多人一遇到CERT_HAS_EXPIRED就直接npm config set strict-ssl false,装完包拍拍屁股走人。这种处理方式不是解决问题的态度,只是在给未来的自己埋雷。等你换一台新电脑、换一个网络环境,同一个坑还会再踩一遍,而且因为当初没有真正定位原因,下次还是会一脸懵。

我的处理顺序永远是:先看时钟,再看源,再看版本,最后才考虑动strict-ssl或者配置额外 CA。这套流程看着慢,实际上每次都不会超过五分钟。真正的专业不是记住某个命令,而是知道在什么情况下该用哪个命令、为什么用它。

如果你按照本文的顺序排查完,问题应该已经解决了。万一你的情况比较特殊,建议把完整的报错信息、npm config list的输出、date的结果都留下来,这些信息对于定位问题至关重要。证书相关的坑虽然烦人,但只要理解了 TLS 校验的基本原理,遇到任何变种都能举一反三。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询