ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

npm install 报 CERT_HAS_EXPIRED?一文讲清 TLS 证书校验原理与排查方案

npm install 报 CERT_HAS_EXPIRED?一文讲清 TLS 证书校验原理与排查方案 这两天接手一个老项目同事在拉依赖的时候终端直接被一连串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 握手客户端会拿到服务端的证书然后做三件事检查证书的有效期notBefore到notAfter看当前时间是否落在区间内。检查证书的签发链逐级回溯到受信任的根证书。检查证书里的域名是否和请求地址匹配。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 月底Lets Encrypt 的旧根证书 DST Root CA X3 到期而它的新根证书 ISRG Root X1 此前主要通过交叉签名来获得信任。过期之后一些比较老的系统、老版本的 Node.js尤其是内置根证书列表没更新的版本在访问使用 Lets Encrypt 证书的站点时就开始报证书链错误表现形式和CERT_HAS_EXPIRED很接近。这类问题用“改时间”“换源”都解决不了唯一干净的办法是升级 Node.js 到维护版本让它带上新的根证书库。所以如果你的 Node 版本特别老比如 8.x、10.x、12.x 的早期版本别犹豫直接升级。3. 实操修复方案按优先级排列3.1 方案 A校准系统时间优先级最高如果确认是本机时间不对修起来很简单。Windows 用户打开“设置 - 时间和语言 - 日期和时间”打开“自动设置时间”再点一下“立即同步”。命令行方式w32tm /resyncmacOS 用户打开“系统设置 - 日期与时间”打开“自动设置日期与时间”。命令行方式sudo systemsetup -setusingnetworktime onLinux 用户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 pingnpm ping会实际请求一次 registry 并返回状态。看到PING和正常响应之后再执行安装就不会报证书错了。这里多说一句换源只是把“请求的地址”换了如果你本机时间有问题换哪个源都一样报错所以顺序一定不要反——先时间后源。另外有些同学喜欢用nrm这类工具管理源也可以但原理是一样的本质就是改registry配置。3.3 方案 C升级 Node.js 与 npm如果你的 Node 版本太老或者 npm 版本存在已知的 TLS 兼容问题升级是最省事的解法。先升级 npmnpm install -g npmlatest但这里有个现实问题npm 本身如果已经因为证书错误跑不动了那npm install -g大概率也会失败。所以更可靠的方式是直接升级 Node.jsnpm 会随附升级。去 Node.js 官网下载 LTS 版本安装包覆盖安装。或者用版本管理工具macOS/Linux 用nvmWindows 用nvm-windows# nvmmacOS / Linux nvm install --lts nvm use --lts # nvm-windowsWindows 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_CERTSC:\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_EXPIREDregistry 是旧镜像域名镜像站证书过期/域名废弃更换registry.npmmirror.comCERT_HAS_EXPIRED公司内网必现网关 SSL 拦截内网 CA 过期更新内网 CA 证书用NODE_EXTRA_CA_CERTS指定UNABLE_TO_VERIFY_LEAF_SIGNATURE证书链不完整/自签名检查代理配置或指定cafileSELF_SIGNED_CERT_IN_CHAIN代理替换了证书但未被信任添加内网 CA 到信任列表2021 年后老 Node 突然报证书错旧根证书到期升级 Node.js 到维护版 LTSERR_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-sslfalse装过一部分包缓存里可能混入了一些不完整的元数据。后续即使证书问题修好了偶尔也会出现校验不一致的怪报错。这时候可以清一下 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或chronyWindows 上用系统自带的“自动设置时间”。每季度检查一次 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 校验的基本原理遇到任何变种都能举一反三。
返回列表