npm 代理设置:npmrc、优先级与安装失败排查

npm 设置代理要同时检查 proxy 和 https-proxy,选定一个网关并实测包下载,npm ping 成功还不够。另讲 npmrc 与环境变量的冲突、用 npm config set proxy 保存、npm config delete 取消,以及如何在不关闭 TLS 校验的前提下修复证书和路由问题。

本页内容

要让 npm 访问源(registry)的请求走代理,proxy 和 https-proxy 两个都要检查,选定一个允许使用的网关;不光要测能不能访问源,还要测包能不能下载下来。npm ping 成功,还不足以证明安装能正常完成。在我们的受控测试中,ping 和包元数据都成功了,tarball 请求却失败了。

本文的观察结果基于 npm 11.19.1、Node 26.10.0 和 macOS 15.7.4,记录于 2026 年 10 月 12 日。九个路由用例和四项配置修改检查都满足了各自写明的验收标准。实验在回环地址上搭了一个 HTTP 源和几个转发代理,不测试 HTTPS、证书和代理认证,也不涉及任何商业服务。

如果 npm install undici 成功了,但你的 Node.js 26 代码随后报 invalid onError method,那是 undici 版本不匹配,与 npm 设置无关:参见 Node 26 报错 invalid onError method。

先分清网关和源

registry 指定的是提供包元数据的服务,也就是源;proxy 和 https-proxy 配置的是访问这个源时经过的网络网关。Nexus 里的“npm proxy repository”(代理仓库)其实是仓库管理器/缓存,对外表现为一个 npm 源,和正向 HTTP 代理不在同一层。公司给的源地址和网关地址是两个独立的设置,要分开填。参见 Sonatype npm 配置、npm 配置。

先在出问题的那个项目目录里运行:

sh
node --version
npm --version
npm config get registry
npm config get proxy https-proxy noproxy
npm config get userconfig globalconfig

请在本地查看这些输出,代理 URL 里可能带有凭据。从源地址看不出请求走的是哪条网络路径,代理值显示为 null 也看不出来:在我们只设置了普通 HTTP_PROXY 环境变量的用例里,npm 对两个配置键都显示 null,请求却仍然走了代理。

分别测试源访问、元数据和 tarball

检查完出问题的项目后,准备好四个变量:NPM_PROXY_URL 是允许使用的网关,NPM_REGISTRY_URL 是允许使用的源,NPM_PACKAGE 是一个不带 scope、按 NAME@VERSION 固定版本的包,NPM_USERCONFIG 是一份允许使用的 npmrc 文件的绝对路径。上面输出的 userconfig 路径可以作为起点;如果源需要认证,要确认所选文件里有对应的认证信息。不需要把任何令牌复制到诊断目录里。

对于私有包或带 scope 的包,这份配置必须保留对应的源和按源配置的认证。@scope:registry 是一项独立的设置:不要以为默认的 --registry 会覆盖它。确认元数据里的 tarball 地址和实际使用的源。下面的基础命令选的是不带 scope 的包,这样可以单独检查网关。参见 npmrc 的 scope 与认证。

HTTPS 的源可以走 HTTP 网关;网关 URL 的协议(scheme)说明的是到网关本身那段连接用的协议。不要把真实密钥直接写在命令里(会留在 shell 历史中),也不要写进共享的日志。用环境变量传值也藏不住:展开后的参数在本机查看进程时仍然看得到。

三项检查都在现有项目之外的同一个新目录中运行。源、npmrc 和代理地址都保持不变,每个请求阶段用一个新的缓存路径:

sh
npm_proxy_check_dir="$(mktemp -d)"
cd "$npm_proxy_check_dir" || exit 1

npm ping --proxy="$NPM_PROXY_URL" --https-proxy="$NPM_PROXY_URL" \
  --registry="$NPM_REGISTRY_URL" --userconfig="$NPM_USERCONFIG" \
  --cache="$npm_proxy_check_dir/cache-ping" \
  --fetch-retries=0 --fetch-timeout=10000

npm view "$NPM_PACKAGE" version dist.tarball --json \
  --proxy="$NPM_PROXY_URL" --https-proxy="$NPM_PROXY_URL" \
  --registry="$NPM_REGISTRY_URL" --userconfig="$NPM_USERCONFIG" \
  --cache="$npm_proxy_check_dir/cache-view" \
  --fetch-retries=0 --fetch-timeout=10000

ping 探测源;view 读取元数据,包括 tarball 地址。两者都不会下载并校验包的 tarball。参见 npm ping、npm view。

第三项检查仍然在这个诊断目录里进行,使用同样的变量:

sh
npm install "$NPM_PACKAGE" --ignore-scripts --no-audit --no-fund \
  --no-package-lock --save=false \
  --proxy="$NPM_PROXY_URL" --https-proxy="$NPM_PROXY_URL" \
  --registry="$NPM_REGISTRY_URL" --userconfig="$NPM_USERCONFIG" \
  --cache="$npm_proxy_check_dir/cache-install" \
  --fetch-retries=0 --fetch-timeout=10000

检查装上的包,名称、版本和内容是否符合预期。我们的实验核对的是确切的文件内容,而不是看到 HTTP 200 或元数据响应成功就算通过。全新的安装缓存可以防止之前缓存的 tarball 悄悄让这项检查通过。如果能拿到网关的请求日志,也要核对:光是包没问题,证明不了它是走哪条路由下载的。保留诊断输出以便复查,结束后删除临时目录。

这次安装禁用了包的安装脚本。它检查的是从源获取包,而不是每一个依赖,也不是项目完整的安装过程。fetch-timeout 加上关闭 fetch 重试,限制的是单次 fetch 的行为,并不等于给整个安装设了十秒的总时限。参见 npm install、npm fetch 选项。

先解决配置冲突,再用 npm config set proxy 保存

对同一个键,npm 文档给出的优先级从高到低依次是:命令行选项、npm_config_* 环境变量,然后是项目级、用户级和全局配置。项目里的 .npmrc 可能会一直覆盖你新加的用户级设置。全局安装模式下不读取项目配置。参见 npm 配置层级(固定版本)、npmrc 文件。

但这套层级解决不了不同键之间的冲突。在我们的 npm 11.19.1 用例中,命令行指定了 --proxy=B,用户级的 https-proxy=A 却仍然生效。两个值在配置解析后都保留了下来,而负责请求源的 fetch 实现选的是 httpsProxy || proxy,即使我们的源是 HTTP 也一样。想只用一个网关,就要检查这两个键并让它们一致。参见 npm-registry-fetch 实现(固定版本)。

临时测试通过后,把不含凭据的示例值保存到目标配置文件:

sh
npm config set proxy=http://proxy.example:8080 \
  https-proxy=http://proxy.example:8080 --location=user

如果要写到项目级配置,就在对应的项目里用 --location=project。凭据不要写进会提交到仓库的 .npmrc。npmrc 的值支持环境变量替换,但查询配置时仍可能显示展开后的值。访问源用的令牌同样按源划分作用范围,而且它们不是代理凭据。参见 npmrc 认证与变量替换。

路由实验测出了什么

路由实验会创建一个模拟源和 A、B 两个代理,提供一个真实生成的包 tarball,并记录每一个实际请求。结果包含确切的命令参数、配置和内容验收结果。

受控冲突观测到的路由/结果诊断价值
没有代理设置,也没有环境变量直连;内容校验通过基线
用户级 npmrc 把两个键都设为 AA;内容校验通过在本实验中用户级配置有效
项目级 B 覆盖用户级 A,两个键都设置B;内容校验通过检查项目文件
npm_config_proxy 和 npm_config_https_proxy 为 A,覆盖项目级 BA;内容校验通过检查进程环境中的配置变量
命令行把两个键都设为 B,覆盖环境变量里的配置 AB;内容校验通过在同一层比较同名的键
只设普通的 HTTP_PROXY=A 环境变量,显示值为 nullA;内容校验通过npm config 的输出不足以证明路由
普通的 NO_PROXY=127.0.0.1 环境变量加用户级代理 A,显示的 noproxy 为空直连;内容校验通过普通环境变量里的绕过规则可能仍然生效
B 放行 ping 和元数据,拒绝 tarballping/view 成功;install 以 E502 失败,包未安装单独检查 tarball 请求
用户级 https-proxy=A,项目级/命令行 proxy=BA;内容校验通过一个键的命令行优先级抹不掉另一个键

八次安装都校验到了预期的包内容。第九次是故意制造的失败,没有装上任何内容;九个用例都满足了各自定义的验收标准。回退去读普通环境变量,是被测的 agent 实现本身的行为。绕过规则的结论只适用于这个精确匹配的 IPv4 回环主机名,不涉及通配符和 CIDR,也不能推广到所有客户端。参见 npm agent(固定版本)。

取消 npm 代理:从目标文件里删除

检查项目的 .npmrc,以及前面输出的用户级和全局配置路径。如果你要删除的是示例中的用户级设置:

sh
npm config delete proxy https-proxy --location=user

项目里的条目仍然可能优先生效。确实要删除项目级设置时,再在项目目录里运行:

sh
npm config delete proxy https-proxy --location=project

我们另外做的四项配置修改检查显示:删除用户级的键后,项目里的值仍然保留;随后再删除项目级的键,这套隔离配置里生效的那一对值才被删掉。npm 文档说删除会从所有文件中移除这些键,但从 npm 11.19.1 的实现和我们的实验看,删除作用的是所选的位置。请明确指定 --location,并验证结果。参见 删除操作的实现(固定版本)、npm config 文档。

还要检查实际运行的进程里的 npm_config_proxy、npm_config_https_proxy、HTTP_PROXY、HTTPS_PROXY 和绕过变量。删掉文件里的条目,不会删掉环境变量的值。代理环境变量指南说明了不同客户端对继承设置的处理有何不同。

仍然失败时按阶段排查

失败阶段下一步检查
连不上网关主机/端口、实际选中的配置和进程的绕过设置
能访问源,tarball 失败dist.tarball 的主机/路径、网关策略和 tarball 请求;注意排除缓存的影响
代理返回 407网关认证;参见 407 指南
源认证错误令牌、源和 scope;与网关凭据分开处理
证书校验错误实际出错的那条连接的主机名和获准的 CA 信任配置
Git 依赖或安装脚本的下载失败HTTP(S) 的 Git 下载参见 Git 设置代理;每个安装脚本用的客户端要分别检查。npm 的源设置决定不了这些路由

如果要信任公司内部的 CA,npm 文档给出的配置项是 cafile 和 strict-ssl。向管理员索取获准的 CA 证书包,并保持 strict-ssl 开启。这些 HTTP 实验没有验证任何 HTTPS 或公司证书信任方面的修复。参见 npm 证书配置。

复现这两个实验

把两个脚本和 README 下载到同一个目录,然后在装有 Node 和 npm 的环境中运行:

sh
node npm-proxy-lab.mjs ./local-results.json
node npm-config-edit-check.mjs ./local-config-results.json

配置脚本和配置检查结果与路由实验的证据一起提供。它们显式使用临时的用户级/全局配置路径和缓存,不会改动你原有的用户配置,网络请求只发往回环地址。记录的环境是 npm 11.19.1 / Node 26.10.0;当前的 v11 文档显示的是更新的版本,所以要把结论用到其他版本上,请先重新跑一遍。运行这些实验不需要额外的 npm 包。HTTPS/CONNECT、代理凭据、私有源认证、Git 依赖、已有的 lockfile 和生命周期脚本中的下载,都不在实验范围内。

ipvolt 销售移动代理和住宅代理,住宅代理每 GB $0.60 起:查看价格。

参考来源与延伸阅读

本指南参考的技术资料。请以你所安装版本的文档以及代理服务商支持的配置为准。