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

Source: https://ipvolt.com/zh/guides/npm-proxy-settings
Markdown: https://ipvolt.com/zh/guides/npm-proxy-settings.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [指南](https://ipvolt.com/zh/guides.md) / npm 代理设置：npmrc、优先级与安装失败排查

集成
发布于: 2026-10-13
阅读约 9 分钟
作者： ipvolt

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](/zh/guides/node-26-invalid-onerror-method)。

## 先分清网关和源

`registry` 指定的是提供包元数据的服务，也就是源；`proxy` 和 `https-proxy` 配置的是访问这个源时经过的网络网关。Nexus 里的“npm proxy repository”（代理仓库）其实是仓库管理器/缓存，对外表现为一个 npm 源，和正向 HTTP 代理不在同一层。公司给的源地址和网关地址是两个独立的设置，要分开填。参见 [Sonatype npm 配置](https://help.sonatype.com/en/configuring-npm.html)、[npm 配置](https://docs.npmjs.com/cli/v11/using-npm/config/)。

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

```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://docs.npmjs.com/cli/v11/configuring-npm/npmrc/)。

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](https://docs.npmjs.com/cli/v11/commands/npm-ping/)、[npm view](https://docs.npmjs.com/cli/v11/commands/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](https://docs.npmjs.com/cli/v11/commands/npm-install/)、[npm fetch 选项](https://docs.npmjs.com/cli/v11/using-npm/config/)。

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

对**同一个键**，npm 文档给出的优先级从高到低依次是：命令行选项、`npm_config_*` 环境变量，然后是项目级、用户级和全局配置。项目里的 `.npmrc` 可能会一直覆盖你新加的用户级设置。全局安装模式下不读取项目配置。参见 [npm 配置层级（固定版本）](https://github.com/npm/cli/blob/v11.19.1/workspaces/config/README.md)、[npmrc 文件](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc/)。

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

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

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

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

## 路由实验测出了什么

[路由实验](https://ipvolt.com/downloads/npm-proxy-settings/npm-proxy-lab.mjs)会创建一个模拟源和 A、B 两个代理，提供一个真实生成的包 tarball，并记录每一个实际请求。[结果](https://ipvolt.com/downloads/npm-proxy-settings/results.json)包含确切的命令参数、配置和内容验收结果。

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

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

## 取消 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`，并验证结果。参见 [删除操作的实现（固定版本）](https://raw.githubusercontent.com/npm/cli/v11.19.1/lib/commands/config.js)、[npm config 文档](https://docs.npmjs.com/cli/v11/commands/npm-config/)。

还要检查实际运行的进程里的 `npm_config_proxy`、`npm_config_https_proxy`、`HTTP_PROXY`、`HTTPS_PROXY` 和绕过变量。删掉文件里的条目，不会删掉环境变量的值。[代理环境变量指南](/zh/guides/proxy-environment-variables)说明了不同客户端对继承设置的处理有何不同。

## 仍然失败时按阶段排查

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

如果要信任公司内部的 CA，npm 文档给出的配置项是 `cafile` 和 `strict-ssl`。向管理员索取获准的 CA 证书包，并保持 `strict-ssl` 开启。这些 HTTP 实验没有验证任何 HTTPS 或公司证书信任方面的修复。参见 [npm 证书配置](https://docs.npmjs.com/cli/v11/using-npm/config/)。

## 复现这两个实验

把两个脚本和 [README](https://ipvolt.com/downloads/npm-proxy-settings/README.md) 下载到同一个目录，然后在装有 Node 和 npm 的环境中运行：

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

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

ipvolt 销售移动代理和住宅代理，住宅代理每 GB $0.60 起：[查看价格](https://ipvolt.com/zh/pricing)。

## 参考来源与延伸阅读

- [npm config](https://docs.npmjs.com/cli/v11/using-npm/config/)
- [npmrc](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc/)
- [Pinned npm registry fetch](https://raw.githubusercontent.com/npm/cli/v11.19.1/node_modules/npm-registry-fetch/lib/index.js)

## 相关指南

- [代理环境变量：HTTP_PROXY 与 NO_PROXY](https://ipvolt.com/zh/guides/proxy-environment-variables.md)
- [不靠猜测修复代理错误 407](https://ipvolt.com/zh/guides/fix-proxy-error-407.md)
- [在 Node.js fetch 中使用代理](https://ipvolt.com/zh/guides/nodejs-fetch-proxy.md)

## 关于 ipvolt

示例使用通用的代理设置，并附上原始技术文档链接。具体产品的行为请向你的服务商确认。ipvolt 销售移动代理和住宅代理，住宅代理每 GB $0.60 起。

[阅读英文原文](https://ipvolt.com/guides/npm-proxy-settings.md)

## 接入你的第一个代理。

移动代理每天 $9 起。住宅代理每 GB $0.60 起。

[购买移动代理](https://ipvolt.com/dashboard/mobile)

[购买住宅代理](https://ipvolt.com/dashboard/residential)

