npm install timeout 的第一步不是删 node_modules,而是把失败阶段切开。npm registry 先返回 package metadata,metadata 里再给出 dist.tarball 下载地址;前者通了,后者仍可能因为 tarball、TLS、代理或 postinstall 下载失败。
这篇按 2026-05-22 比对过的 npm v11、pnpm 当前文档写。团队里最好用同一张表记录命令输出,不然本地、CI、公司网络三种结果会互相打架。
##确认卡在 metadata 还是 tarball?
npm registry 的安装链路不是一个请求。metadata 请求通常是 GET https://registry.npmjs.org/:package,安装用的精简响应里会包含 dist.tarball、dist.shasum 或 integrity。所以 npm ping 成功,只能说明 registry 入口可达。
用一个公开小包做基准,再用出问题的包复测:
npm config get registry
pnpm config get registry
curl -I -L https://registry.npmjs.org/ --connect-timeout 10 -m 20
npm ping --registry=https://registry.npmjs.org/
npm view left-pad version --registry=https://registry.npmjs.org/
如果 npm view 正常,但 install 卡在某个包,继续查 tarball、postinstall 和私有 scope。遇到 ETIMEDOUT、ECONNRESET、EAI_AGAIN、ERR_SOCKET_TIMEOUT 时,把完整错误码贴进 issue,不要只写“网络失败”。
| 现象 | 更可能的位置 | 下一条命令 |
|---|---|---|
curl registry 超时 | DNS、出口网络、registry 可用性 | 查 status.npmjs.org 和 DNS |
npm view 正常,install 慢 | tarball、依赖体积、postinstall | npm install --verbose |
| 只私有包失败 | scoped registry、token、权限 | npm config list -l |
| 只 pnpm 失败 | store、lockfile、workspace | pnpm install --reporter=append-only |
| 只 CI 失败 | cache、secret、runner 网络 | 打印 Node/pnpm/registry/lockfile |
.npmrc 应该查哪几层?
npm 官方把配置来源拆成命令行 flag、环境变量和四类 npmrc:项目 .npmrc、用户 ~/.npmrc、全局 $PREFIX/etc/npmrc、内置 npmrc。更具体的配置会覆盖更宽的配置;官方示例也明确用户配置优先于全局配置。
排查时先打印真实生效值:
npm config list -l
npm config get registry
npm config get userconfig
npm config get globalconfig
pnpm config list
项目 .npmrc 只写团队一致的 registry、scope、重试上限,不写个人出口、个人 token、临时证书路径。私有包要显式 scoped,认证字段要按 registry host 限定:
registry=https://registry.npmjs.org/
@your-scope:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
fetch-retries=2
fetch-timeout=300000
npm 文档要求 _auth、_authToken、username、_password、email、cafile、certfile、keyfile 这类字段按 URI fragment 绑定 registry,例如 //registry.npmjs.org/:_authToken=。没绑定 host 的 token 是团队安全事故,不是便利配置。
npm 和 pnpm 的重试参数怎么对齐?
npm 与 pnpm 的配置名相似,但默认 timeout 不一样。npm v11 文档里 fetch-timeout 默认是 300000ms;pnpm settings 里 fetchTimeout 默认是 60000ms。拿 pnpm 的 60 秒失败去判断 npm 的 5 分钟阈值,会误判。
| 维度 | npm v11 | pnpm settings | 排查意义 |
|---|---|---|---|
| 默认 registry | https://registry.npmjs.org/ | 可用 registries.default 配 | 确认默认入口一致 |
| 重试次数 | fetch-retries=2 | fetchRetries=2 | 不要无限重试 |
| 最小重试间隔 | fetch-retry-mintimeout=10000 | fetchRetryMintimeout=10000 | 10 秒级抖动可被吸收 |
| 最大重试间隔 | fetch-retry-maxtimeout=60000 | fetchRetryMaxtimeout=60000 | 超过 1 分钟仍失败要查链路 |
| 单次超时 | fetch-timeout=300000 | fetchTimeout=60000 | pnpm 默认更快失败 |
| 并发 | 依赖 npm 实现 | networkConcurrency 默认 auto,16-64 上限区间 | 并发过高会放大弱网络问题 |
临时拉长 timeout 可以帮助确认是不是慢链路,但别把它当长期方案。CI 里长期写 10 分钟 timeout,会让 token 过期、scope 写错、证书错误这类硬失败晚 10 分钟才暴露。
TLS、证书和公司代理怎么分开查?
TLS 证书问题常被写成“网络超时”,但错误码不同。看到 UNABLE_TO_VERIFY_LEAF_SIGNATURE、SELF_SIGNED_CERT_IN_CHAIN、CERT_HAS_EXPIRED,优先看证书链,不要先换 registry。
Node.js 官方 CLI 文档列出 NODE_EXTRA_CA_CERTS=file,可把额外 CA 证书加入 Node 信任链。npm 侧也有 strict-ssl、cafile、ca;pnpm settings 里 strictSsl 默认是 true,也支持 registry 级别证书字段。
# 临时验证证书文件是否解决问题
NODE_EXTRA_CA_CERTS=/path/to/corp-ca.pem npm install
# npm 侧查看 TLS 相关配置
npm config get strict-ssl
npm config get cafile
npm config get ca
strict-ssl=false 只能作为一次性定位手段,不能提交到项目 .npmrc。真正可维护的写法是把公司 CA 文件放到受控路径,在 CI secret 或 runner 镜像中注入,再记录证书更新人和到期日。
proxy 环境变量和 npm 配置谁优先?
npm 文档里的 proxy、https-proxy 默认是 null,noproxy 默认读取 NO_PROXY 环境变量。pnpm settings 也有 httpsProxy、httpProxy、noProxy。问题在于:项目 .npmrc、用户 .npmrc、shell 环境变量和 CI secret 可能同时存在。
把当前 shell 的代理变量打出来:
env | grep -i '_proxy'
npm config get proxy
npm config get https-proxy
npm config get noproxy
pnpm config get httpsProxy
pnpm config get noProxy
临时排查用命令前缀,别污染仓库:
HTTPS_PROXY=http://127.0.0.1:7890 pnpm install --reporter=append-only
NO_PROXY=localhost,127.0.0.1,.corp.example.com npm install
如果团队同时依赖 GitHub Packages、npm registry、Docker Registry、Cloudflare 和 GitHub Actions,网络路径抖动会在 install、deploy、Dashboard 三个环节轮流出现,把 proxy、token、证书、缓存写成团队规范,再判断是否是网络路径问题。
pnpm store、lockfile 和 workspace 怎么排?
pnpm 的失败不一定发生在网络层。store 损坏、workspace 递归构建、pnpm-lock.yaml 漂移,都能表现成“install 卡住”,看版本、store、锁文件,再决定要不要清缓存。
pnpm -v
pnpm store path
pnpm store status
pnpm install --frozen-lockfile --reporter=append-only
pnpm store status 用来检查 store 中包内容是否被改过,退出码 0 表示内容仍匹配原始解包状态。pnpm store prune 会删除当前机器上没有项目引用的包;如果后面又需要这些包,pnpm 会重新下载。
| 动作 | 适合什么时候用 | 不适合什么时候用 |
|---|---|---|
pnpm store path | 确认当前 store 位置 | 不能证明缓存健康 |
pnpm store status | 怀疑 store 被改或损坏 | 不能修网络超时 |
pnpm store prune | 磁盘太大、旧项目很多 | CI 每次都跑会放大下载量 |
删除 node_modules | 链接结构混乱、包升级后异常 | 不能替代 lockfile 排查 |
删除 pnpm-lock.yaml | 只有确认要重新解依赖 | 排障第一步绝对不该做 |
monorepo 还要看 workspace 的 postinstall。某个包下载二进制、编译原生模块、访问 GitHub Release,都会绕开 npm registry 这一层。
CI 和本地结果不一致时查什么?
pnpm install 文档明确:CI 中有 lockfile 时默认更严格,--frozen-lockfile 会阻止更新 pnpm-lock.yaml,manifest 和 lockfile 不匹配就失败。本地跑通,只能说明你电脑上的配置和缓存可用。
CI 里建议固定版本并打印证据:
node -v
corepack enable
corepack prepare [email protected] --activate
pnpm -v
npm config get registry
pnpm config get registry
pnpm store path
pnpm install --frozen-lockfile --prefer-offline --reporter=append-only
--prefer-offline 会跳过缓存数据的新鲜度检查,但缺包时仍会访问服务端;--offline 只从 store 安装,缺包就失败。两者不要混用成“网络修复方案”。
CI runbook 至少记录 6 个字段:Node 版本、包管理器版本、registry、lockfile hash、cache key、secret 名称。失败日志保留第一次 timeout 前后的 50 行,比连续重跑 5 次更有价值。
如果还没恢复,怎么交接给团队?
交接时不要写“npm 网络不行”。把失败缩到一个最小复现,包含包名、版本、registry、错误码、地区/runner、是否私有包、是否 postinstall。这样后端、运维和前端能对同一条证据说话。
如果 install、deploy 和 Dashboard 访问都在同一批团队成员身上反复失败,可以把核心后台和 CI 访问固定到同一套工作网络,用海外服务跑 GitHub Actions / Cloudflare 的稳定线路承载这些开发任务;它不替代 token、证书、缓存和 lockfile 规范,只减少排查变量。
| 要记录的字段 | 示例 | 谁来判断 |
|---|---|---|
| 包管理器与版本 | npm 11 / pnpm 10.12.1 | 前端负责人 |
| 失败阶段 | metadata / tarball / postinstall | 前端 + DevOps |
| 错误码 | ETIMEDOUT / EAI_AGAIN / CERT_HAS_EXPIRED | DevOps |
| registry 与 scope | npmjs / GitHub Packages / 私有 Verdaccio | 包维护人 |
| TLS / proxy | cafile、NODE_EXTRA_CA_CERTS、HTTPS_PROXY | 运维 |
| CI 证据 | runner、cache key、secret 名称 | CI 维护人 |
如果 npm status 页显示 registry installation 服务异常,暂停大规模 CI 重跑。状态页正常时,就按这张表继续缩小范围:默认 registry、scoped registry、TLS、代理、pnpm store、CI lockfile。
相关阅读
- GitHub clone 很慢:DNS、代理和 SSH 排查 — Git仓库克隆慢的排查
- Docker pull timeout:开发者代理排查 — Docker镜像拉取失败排查
- Mac 开发代理配置:Terminal Git npm Docker — Mac开发环境的完整代理配置
容易被忽略的坑
strict-ssl=false 能不能长期用?
不能。它会降低 TLS 校验,最多用于一次性确认是不是公司证书链问题。长期方案是配置受控 CA:npm 用 cafile,Node 运行环境用 NODE_EXTRA_CA_CERTS,CI 镜像同步证书文件。
monorepo 的 postinstall 暗坑
某个包下载二进制、编译原生模块、访问 GitHub Release,都会绕开 npm registry 这一层。卡在 postinstall 时用 verbose 日志确认最后一个执行脚本,再决定是查代理、加证书还是锁版本。
scoped registry 和 token 的常见错配
@scope:registry=、token host、包名 scope 三者必须同时匹配。一个项目同时用 npmjs、GitHub Packages 和私有 registry 时,未限定 token 或写错 scope 会表现成 401、404 或超时。