系统、Git、npm、Maven、Docker 与 IDE 代理工具手册
浏览器能联网,构建链为什么全部失败
开发机接入企业网络后,浏览器可以打开外网,git fetch 却超时,npm install 报证书错误,Maven 下载依赖返回 407,Docker 又提示 x509: certificate signed by unknown authority。这不是一个代理开关失效,而是多个进程读取了不同配置源、使用了不同信任库。
排障第一步不是把同一条代理 URL 写进所有文件,而是标记失败发生在哪个进程。浏览器通常读取系统用户代理;Windows 服务可能读取 WinHTTP;Git 使用 libcurl 和 Git 配置;npm 同时受 npm 配置与环境变量影响;Maven 在 JVM 内读取 settings.xml 和 Java 信任库;Docker daemon、build 容器和运行容器则是三个独立执行环境。IDE 的插件下载成功,只能证明 IDE 自己的网络栈可用。
拿到网络团队提供的代理类型、主机、端口、认证方式和企业 CA 指纹后,先建立两个域名集合:外部 registry、代码托管和依赖仓库必须走代理;localhost、内网 Git、制品库、镜像仓库和服务发现域名必须直连。示例中的 proxy.example.test、用户名和密码都只是占位符,真实凭证不能进入命令历史、仓库或工单。
从临时环境变量开始,避免一上来污染全局
Windows 先查看 WinHTTP,而不是直接覆盖它:
netsh winhttp show proxy
Get-ChildItem Env:*_PROXYmacOS 的系统代理按网络服务配置,切换 Wi-Fi、网线或 VPN 后结果可能不同。终端实验优先只给当前 shell 设置变量:
export http_proxy=http://proxy.example.test:8080
export HTTPS_PROXY=http://proxy.example.test:8080
export NO_PROXY=localhost,127.0.0.1,::1,.corp.example.test
curl -v https://registry.npmjs.org/-/ping
curl -v --noproxy '*' https://repo.corp.example.test/health这里刻意使用小写 http_proxy:curl 出于 CGI 安全原因不接受大写 HTTP_PROXY,其他协议变量才同时接受大小写,且小写值优先。团队不能把一组大写变量当成所有客户端的统一契约;需要兼容只认大写变量的工具时,应在该工具的启动入口单独注入并做回归。HTTPS_PROXY 通常表示“访问 HTTPS 目标时使用的代理”,其值仍常是 http://,因为客户端先用 HTTP CONNECT 建隧道;把它机械改成 https:// 会改成与代理本身建立 TLS。
curl 的命令参数优先于环境变量:--proxy 选择代理,--noproxy 覆盖 NO_PROXY。NO_PROXY 决定直连目标,但后缀、通配符、端口、CIDR 和 IPv6 的解释并不统一。先用精确主机名验证,再逐步合并后缀规则;不要因为 curl 的 CIDR 规则成立,就推断 npm、JVM 或 IDE 也会采用相同匹配结果。
成功证据不能只有状态码。curl -v 中外部地址应先连接代理并出现 CONNECT,内网地址应直接连接目标 IP;代理审计日志也应出现外部请求而没有内网请求。若返回 407 Proxy Authentication Required,网络已经走到代理,失败点是认证;若 CONNECT 成功后出现证书错误,认证已通过,问题转到客户端信任链。
Git:配置来源比配置值更重要
Git 的 http.proxy 会覆盖常见代理环境变量,仓库级、用户级和系统级配置还可能互相覆盖。先看值从哪里来:
git config --show-origin --show-scope --get-regexp \
'^(http\..*proxy|remote\..*\.proxy|http\..*sslCAInfo)$'
git remote -v只为本次命令指定代理,不写入任何配置文件:
git -c http.proxy=http://proxy.example.test:8080 \
ls-remote https://github.com/git/git.git HEAD预期输出是一行 commit ID 和 HEAD。反向实验把代理端口换成确定未监听的 127.0.0.1:9,应出现无法连接代理的错误;随后用 git -c http.proxy= 执行同一请求,若直连策略允许且命令恢复,便证明失败来自代理路径,而不是仓库权限或远端不存在。这个实验使用命令级覆盖,不会改动全局状态。
确需持久化时,团队代理可写用户级 http.proxy;某个内网 remote 必须直连时,可在仓库中把 remote.origin.proxy 设为空。remote.<name>.proxy 只作用于 HTTP、HTTPS 和 FTP remote,不会替 SSH remote 配代理。带用户名但不带密码的代理 URL可让 Git 通过凭证机制获取密码,优于把密码明文写进 .gitconfig。
企业 TLS 检查导致证书链变化时,先确认 Git 使用的 TLS backend:
git config --show-origin --get http.sslBackend
git config --global http.sslCAInfo /secure/path/company-ca.pemOpenSSL backend 可用 http.sslCAInfo 指向受控 CA bundle;Windows 上的 Schannel backend 默认使用 Windows 证书存储,并默认不让 http.sslCAInfo 覆盖它。只有明确需要文件 bundle 且完成公私仓库回归时,才评估 http.schannelUseSSLCAInfo=true,因为这会改变原有系统信任来源。访问 HTTPS 代理本身所需的 CA 则是 http.proxySSLCAInfo,它与验证目标站点的 http.sslCAInfo 不是同一个链路。
http.sslVerify=false 只能证明“证书校验参与了失败”,不能成为修复。它同时取消服务器身份校验,容易把一次临时排障变成长期中间人风险。
npm:同时检查 registry、代理和 CA
npm 的成功路径不仅取决于代理。项目 .npmrc、用户配置、全局配置、环境变量和命令参数可能指向不同 registry。先把来源和值放到一起看:
npm config get userconfig
npm config get globalconfig
npm config get registry
npm config get proxy
npm config get https-proxy
npm config get noproxy
npm config get strict-ssl
npm config get cafilenpm 的确定性优先级是命令参数、npm_config_* 环境变量、项目 .npmrc、用户 .npmrc、全局配置,最后才是内置默认值。proxy、https-proxy 和 noproxy 沿用这套配置优先级;没有显式 npm 配置时,底层请求库还会读取常见代理环境变量。因此排障时只打印一个最终值不够,还要用 npm config get userconfig、globalconfig 和当前项目文件定位来源。
命令级正向验证不会留下持久配置:
npm ping \
--registry=https://registry.npmjs.org/ \
--proxy=http://proxy.example.test:8080 \
--https-proxy=http://proxy.example.test:8080预期出现 PONG。把代理改为 http://127.0.0.1:9 后应得到连接失败;恢复合规代理后成功,说明 registry 本身可达。若错误变成 SELF_SIGNED_CERT_IN_CHAIN 或 UNABLE_TO_GET_ISSUER_CERT_LOCALLY,代理连接已建立,下一步是把包含一个或多个 CA 的 PEM 文件路径写入 cafile,而不是把 strict-ssl 关掉。
npm config set cafile /secure/path/company-ca.pem --location=user
npm config set proxy http://proxy.example.test:8080 --location=user
npm config set https-proxy http://proxy.example.test:8080 --location=usernoproxy 默认读取 NO_PROXY,并接受逗号分隔的域名后缀。设置 cafile 后要同时验证公共与私有 registry,防止只放入企业根而意外丢失原本需要的公共根。私有 registry 同时涉及网络、CA 和 npm token:连通性修好后仍返回 401,应检查当前 registry 对应的 token 配置,不能继续改代理。输出 npm config list -l 前要脱敏,因为配置来源中可能出现认证信息。
Maven:代理走 settings.xml,证书走当前 JDK
Maven 的用户配置通常位于 ~/.m2/settings.xml,全局配置位于 Maven 安装目录的 conf/settings.xml。help:effective-settings 能展示合并结果,但输出也可能包含敏感配置,保存前必须清理。一个可工作的代理片段如下:
<settings>
<proxies>
<proxy>
<id>corp-proxy</id>
<active>true</active>
<protocol>http</protocol>
<host>proxy.example.test</host>
<port>8080</port>
<nonProxyHosts>localhost|127.0.0.1|*.corp.example.test</nonProxyHosts>
</proxy>
</proxies>
</settings>active 决定该项是否参与选择,protocol 描述与代理的协议,host/port 决定连接点,nonProxyHosts 使用 | 分隔并支持通配符。代理需要认证时,username 和 password 属于用户秘密,不应放进项目 pom.xml;优先遵循组织的 Maven 密码加密或外部凭证流程。
用户 settings.xml 会覆盖 Maven 安装目录中的全局 settings,同一时刻只应有一个 active proxy。Maven 官方入口是 <proxies>;Java 的 http.proxyHost 一类系统属性是否生效取决于具体 transport,不能作为跨 Maven 版本的优先级规则。Maven 3 的传统加密依赖可恢复的 master secret,不能等同于密钥托管;Maven 4 的 mvnenc 支持更明确的 secret source。无论采用哪条线,都先用交互式输入,避免把原始密码放进 argv,并限制 settings 与安全配置文件的权限。
mvn help:effective-settings
mvn -X -DskipTests dependency:go-offline-X 日志中出现 407 说明代理拒绝认证,仓库 401 则是制品库认证失败,两者不是一件事。PKIX path building failed 表示执行 Maven 的那个 JDK 不信任证书链。先用 mvn -version 确认 Java home,再把经核验的企业 CA 导入专用 truststore 或按平台流程分发;不要只给另一个 JDK 导入后宣布修复。
反向实验可将 active 临时改为 false 后再次执行 dependency:go-offline。若企业网络禁止直连,预期错误从代理响应变成连接超时或拒绝;恢复为 true 后重新得到代理或仓库响应,便证明 effective settings 已生效。实验只改用户配置的临时副本,并用 mvn -s <temp-settings.xml> 指向它,避免损坏日常配置。
Docker:pull、build 和 runtime 分三次验
docker pull 的网络请求由 Docker daemon 发出。在原生 Docker Engine 上,推荐在 daemon.json 配置:
{
"proxies": {
"http-proxy": "http://proxy.example.test:8080",
"https-proxy": "http://proxy.example.test:8080",
"no-proxy": "localhost,127.0.0.1,.corp.example.test"
}
}修改后必须重启 daemon。Docker Desktop 不读取这里的 daemon 代理字段,要在 Desktop 设置中配置。验证入口是 docker pull hello-world;若这里报 x509,需要修 Docker host/VM 访问 registry 时的 CA,给运行容器加 CA 没有用。
原生 Engine 还可以从启动环境读取代理,但 daemon.json 的直接配置优先于这些环境变量。私有 registry 使用自定义 CA 时,Linux Engine 按 registry 主机名和端口读取 /etc/docker/certs.d/<host[:port]>/ca.crt;.crt 是 CA,.cert 与同名 .key 是客户端证书对,扩展名放错会被解释成另一种身份。Windows Server 和 Windows containers 在存在自定义根时对系统默认证书的合并行为不同,不能照搬 Linux 结论。完成变更后要分别验证公共镜像与私有 registry,避免定点 CA 配置缩小原有信任集合。
~/.docker/config.json 的 proxies.default 是另一层:它会给新容器设置代理环境变量,并预填 build 的代理参数,不会配置 Docker CLI 或 daemon:
{
"proxies": {
"default": {
"httpProxy": "http://proxy.example.test:8080",
"httpsProxy": "http://proxy.example.test:8080",
"noProxy": "localhost,127.0.0.1,.corp.example.test"
}
}
}先用一次性构建观察 build 环境:
docker build --no-cache --progress=plain - <<'EOF'
FROM alpine
RUN env | grep -i _PROXY
EOF预期日志显示代理 build arguments。不要在 Dockerfile 中用 ENV HTTP_PROXY=... 固化代理,它会进入镜像配置,并可能暴露内网地址或认证信息。build 能下载依赖后,再验证新容器:
docker run --rm alpine sh -c 'env | grep -i proxy'旧容器不会自动获得新值,必须重建。容器内应用若访问被 TLS 检查的 HTTPS 站点,还要把企业 CA 安装到镜像或运行时信任库;这与环境变量是否存在是两个关口。docker inspect 和远程 API 可读取明文环境变量,因此带密码的代理 URL 不应通过这条路径长期注入。
IDE:验证自身网络,也验证它启动的进程
VS Code 和 JetBrains 的代理设置主要覆盖更新、扩展市场、许可证或 IDE 自有 HTTP 客户端。内置终端通常继承 IDE 启动时的环境,已经打开的窗口不会自动获得后来修改的 shell 变量。IDE 调用内置 Git、Maven wrapper、远程容器或语言服务器时,还可能各自读取不同配置。
验证时在 IDE 内完成三件事:扩展市场可访问;内置终端打印的代理变量与外部终端一致;从 IDE 发起的 Git/npm/Maven 命令能在相应工具日志中看到正确配置来源。AI 插件、代码索引和远程开发通道可能把源代码、日志、请求内容送往外部服务,代理打通不等于数据出境获得批准,必须单独执行组织的访问控制和数据策略。
项目接入与凭证边界
仓库只应保存不含秘密的代理策略:哪些域名直连、各工具读哪个配置源、企业 CA 由什么受控渠道分发、如何验证和回滚。.env.example 可以使用 http://proxy.example.test:8080 这类占位符,但实际 .env、用户 .npmrc、settings.xml、Docker client config 和 IDE settings 必须被排除在版本控制之外。
代理密码很容易出现在 shell history、进程参数、调试日志、docker inspect、镜像历史和设置同步中。优先使用 Kerberos/Negotiate、SSO、系统凭证库、Git credential helper、短期 token 或 CI secret 注入。必须使用 URL 用户信息时,注意特殊字符需要编码,且不要把测试命令粘到工单。企业 CA 的来源、用途、指纹、alias、安装范围和撤销方式应可审计;临时抓包 CA 不能冒充企业 CA 分发。
CI runner 与开发机也不能共享个人凭证。runner 使用服务身份、最小权限和独立 CA bundle,日志对代理 URL 与认证头做遮罩。代理策略变更要同时验证外部依赖和内部制品库,避免 NO_PROXY 误改造成内网流量外送,或让外部依赖绕过审计出口。
清理后要证明旧配置不再生效
实验结束先关闭使用代理的长驻 IDE、终端和容器,再删除对应层配置。shell 同时清理实际注入过的大小写变量,例如 unset http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY ALL_PROXY all_proxy;Git 用 git config --show-origin 找到真正来源,再对相同 scope 执行 --unset;npm 对用户层执行 npm config delete proxy --location=user、npm config delete https-proxy --location=user 和必要的 cafile 清理;Maven 停用或删除临时 <proxy>;Docker 删除 daemon 或 client 代理后重启 daemon,并重建容器。
Windows WinHTTP 只有在确认该层由本次实验修改时才执行 netsh winhttp reset proxy,系统用户代理和 PAC 也要恢复原值。CA 删除同样按信任库逐层进行:OS、JDK、Node、Docker host 和容器镜像互不等价。
回滚验收需要反证。再次查看 Git 配置来源、npm 配置、effective settings、Docker inspect 和环境变量,不应再出现旧代理;连接旧代理审计日志时不应产生新流量;内网地址继续直连,外部地址按组织默认策略成功或明确失败。若只删除配置文件却没有重启长驻进程,旧环境和连接池仍可能继续工作,不能算清理完成。
架构师如何决定配置落在哪一层
代理配置越靠近系统,覆盖面越大、使用简单,但误伤和数据外送半径也越大;越靠近单个命令,隔离性和可审计性越好,维护成本更高。个人短时排障优先命令参数或当前 shell;稳定的开发工具配置放用户层;项目仓库只保存无秘密的策略模板;组织出口、PAC、企业 CA 和 Docker Desktop 策略由终端管理平台维护。不要用项目脚本静默修改系统代理或机器根证书。
NO_PROXY 是最容易制造隐蔽故障的字段。不同实现对 .example.test、*.example.test、CIDR、端口和大小写的处理可能不同,因此治理对象不是“一条万能字符串”,而是一组必须直连的目标和每个关键客户端的回归结果。每次变更都跑 Git 外部 remote、npm 公私 registry、Maven 公私仓库、Docker pull/build/runtime 和 IDE 更新的成对实验。
容量与成本也会反过来影响设计。集中代理要承担依赖下载、镜像层和并发构建的带宽,重复冷下载会放大流量费用与排队时间;本地缓存、制品代理仓库和 registry mirror 能降成本,但会引入缓存一致性、许可证、存储和供应链治理。客户端应记录连接耗时、代理 407、TLS 失败、下载字节与重试次数,平台侧观察并发连接、出口带宽、缓存命中率和失败率。告警阈值来自团队基线与构建 SLO,不使用脱离负载的固定数字。
长期治理的完成标志是每个配置都有所有者、来源、适用进程、凭证载体、CA 指纹、验证命令和撤销动作。工具或运行时升级后,重新执行正向代理、无效代理、错误 CA、内网绕过和旧进程残留实验;只有失败能够被准确分到网络、代理认证、目标认证、证书信任或配置优先级,代理才从个人经验变成可维护的工程能力。
