技术指南

终端网络不一致:对齐 Windows、WSL、远程主机与容器

按真实执行位置拆分 DNS、代理、TLS 与应用证据,定位 Windows、WSL、远程主机和容器之间的网络差异。

浏览器能打开网页,只证明浏览器那条路径。Windows 终端、WSL、Remote SSH、容器、npm、Git 和 API 客户端可能运行在不同主机或网络命名空间,并分别读取代理、DNS、证书与应用配置。排障应先定位真正发起请求的进程,再在同一执行位置逐层取证。

来源提供环境清单,本文改写为可判定的证据矩阵

归档来源覆盖 Windows、PowerShell、WSL、Cursor、Remote SSH、Docker、npm、Git 与 OpenAI API,可保留的是执行位置隔离、分层探测、工具配置独立和不要禁用 TLS。本文不沿用公网 IP/地理位置作为主诊断、固定 7890 代理端口、一次 HTTP 状态代表全部网络正常、随手拉取镜像、未验证就写入全局代理,以及对账号或服务可用性的推测。

先回答:哪一个进程在发请求

表面入口通常执行位置需要就地核对
Windows Terminal / PowerShellWindows 主机Windows DNS、WinHTTP/应用代理、Windows 证书库
WSL shellWSL 发行版WSL 网络模式、resolv.conf、Linux 环境与证书库
VS Code Remote SSH 终端远程 SSH 主机远端 DNS、出口、代理、证书与文件
Dev Container 终端容器容器环境、DNS、路由、挂载证书与 NO_PROXY
npm / Git / API SDK启动该工具的进程工具配置、环境变量、运行目录与实际端点

VS Code 官方文档明确说明 Remote SSH 连接后,命令和多数扩展直接在远端运行,新的终端也自动运行在远端;本地代理设置不会自动复用于远端。先记录窗口状态、主机名、用户和目录,避免在本地修改一个并未发请求的环境。

为每次探测保存不含秘密的执行指纹

只记录位置与 set/missing;内部主机名、地址和代理存在性仍按受限运行证据保护
date --utc --iso-8601=seconds
printf 'host=%s user=%s cwd=%s\n' "$(hostname)" "$(id -un)" "$PWD"
uname -a
printf 'wsl=%s container=%s\n' \
  "${WSL_DISTRO_NAME:+yes}" "$(test -f /.dockerenv && echo yes || echo no)"
for name in HTTP_PROXY HTTPS_PROXY NO_PROXY; do
  if printenv "$name" >/dev/null; then printf '%s=set\n' "$name"; else printf '%s=missing\n' "$name"; fi
done
PowerShell、WSL 与编辑器集成终端各执行一次;不要输出代理凭据或完整环境
$now = Get-Date -AsUTC -Format o
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
[pscustomobject]@{ time=$now; host=$env:COMPUTERNAME; user=$identity; cwd=$PWD.Path }
wsl.exe --status
wsl.exe --list --verbose
Get-Command curl.exe | Select-Object -ExpandProperty Source

WSL 不是 Windows shell 的同义词

Microsoft 当前文档说明 WSL 默认可使用 NAT;Windows 11 22H2 及更高版本还支持 mirrored 模式。dnsTunneling 改变 WSL 内 DNS 请求如何交给 Windows,autoProxy 可把 Windows HTTP 代理信息交给 WSL;这些设置有版本前提,并可能需要重启 WSL 实例后才生效。先记录当前版本与模式,不要看到 VPN 或 DNS 问题就批量改 .wslconfig。

  • 在 Windows 与目标 WSL 发行版分别解析同一主机,记录答案、地址族和时间。
  • 分别运行同一个无副作用 HTTPS 探针,保存 curl 版本、远端地址、状态和阶段耗时。
  • 核对 WSL 中代理变量是否真的存在,但不要打印带凭据的值。
  • 若准备修改 networkingMode、dnsTunneling 或 autoProxy,先保存原配置、核对版本支持并写明回滚值。
  • 用 wsl --shutdown 后的全新实例验证设置,不能用旧进程外推。

Remote SSH 的网络证据来自远端,不来自本地桌面

Remote SSH 窗口中的终端、workspace 扩展和调试进程通常使用远端主机的 DNS、出口与信任库。本地浏览器成功、本地 curl 成功,甚至 SSH 隧道本身成功,都不能证明远端扩展能下载依赖或访问 API。远端 host-specific settings、shell profile、systemd 服务和容器还可能继续分叉。

示意域名必须替换为获准的无副作用端点;在实际失败的远端终端执行
printf 'host=%s user=%s cwd=%s\n' "$(hostname)" "$(id -un)" "$PWD"
getent ahosts api.example.invalid
curl --silent --show-error --output /dev/null \
  --connect-timeout 5 --max-time 20 \
  --write-out 'remote=%{remote_ip} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} status=%{response_code} total=%{time_total}\n' \
  https://api.example.invalid/health

按请求阶段分类,不用“能上网”代替证据

阶段可观察证据仍不能证明
配置目标 scheme/host/port、代理是否设置、进程身份DNS 或连接成功
DNS解析器、A/AAAA、耗时与错误目标端口、TLS 或 HTTP 健康
TCP目标地址、连接成功/拒绝/超时证书或应用路由正确
TLSSNI、链、主机名、有效期与验证结果认证、权限或业务成功
HTTP响应来源、状态、头部、请求 ID凭据正确或完整功能正常
应用npm/Git/SDK 的端点与结构化错误其他工具共享相同配置

给连接和整次传输分别设置截止时间

curl 当前手册说明 connect timeout 覆盖 DNS、TCP 与 TLS/QUIC 握手阶段,而 max time 限制整次传输。两者要同时设置,才能区分建立连接慢和响应慢,并阻止探针无限等待。不要在带 Authorization 的请求上默认使用 verbose;先对无秘密健康端点取证。

同时保存 curl exit 与 HTTP status;没有可信 HTTP 响应时 status 不能解释为应用结果
set +e
curl --silent --show-error --output /dev/null \
  --connect-timeout 5 --max-time 20 \
  --write-out 'code=%{response_code} remote=%{remote_ip} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
  https://service.example/health
rc=$?
set -e
printf 'curl_exit=%s\n' "$rc"

先做单进程代理实验,再决定是否持久化

只影响这一条命令;代理地址和绕过范围是示意,真实凭据不得进入 shell 历史或共享日志
HTTPS_PROXY=http://proxy.example:3128 \
NO_PROXY=localhost,127.0.0.1,.internal.example \
  curl --silent --show-error --output /dev/null \
  --connect-timeout 5 --max-time 20 \
  --write-out 'remote=%{remote_ip} status=%{response_code}\n' \
  https://service.example/health

用相同 URL 比较 direct、显式 proxy 和 NO_PROXY bypass,记录最终远端、响应来源与时间。NO_PROXY 的格式并没有跨客户端统一标准,必须按实际 curl、npm、Git、运行时和容器逐一验证。只有单进程实验通过、绕过范围最小且回滚清晰时,才考虑写入 shell、WSL、编辑器、systemd 或容器配置。

npm 同时读取环境变量和多层 npmrc

npm 当前文档列出项目、用户、全局与内置配置文件;用户配置可由 NPM_CONFIG_USERCONFIG 或 --userconfig 定向。https-proxy 也会受 HTTPS_PROXY、HTTP_PROXY 等环境变量影响。排障时使用一次性 userconfig 验证候选,不要直接覆盖真实 ~/.npmrc;npm ping 只检查当前配置的 registry 及认证结果,不代表 Git、API 或任意包安装路径。

使用测试 registry 或获准公共 registry;配置中可能含 token,文件权限与诊断输出都要受控
probe_npmrc=$(mktemp)
trap 'rm -f "$probe_npmrc"' EXIT
NPM_CONFIG_USERCONFIG="$probe_npmrc" \
  npm config set proxy http://proxy.example:3128 --location=user
NPM_CONFIG_USERCONFIG="$probe_npmrc" npm ping \
  --fetch-timeout=10000
NPM_CONFIG_USERCONFIG="$probe_npmrc" \
  npm config delete proxy --location=user

Git 代理是独立配置,TLS 验证默认应保持开启

Git 当前文档说明 http.proxy 可覆盖环境代理,也可按 remote 配置;代理必须透明,否则会破坏 Git 协议。http.sslVerify 默认 true。优先用单命令 `git -c http.proxy=...` 对只读仓库执行 ls-remote,再决定是否写入仓库或用户配置;不要以关闭 sslVerify 作为代理证书修复。

只读探针仍会访问远端;使用获准仓库,且不要在不受信目录中运行钩子或任意构建
git -c http.proxy=http://proxy.example:3128 \
  ls-remote --exit-code https://git.example/project/repo.git HEAD

# 检查来源时先脱敏输出,避免代理账号密码进入工单
git config --show-origin --get-regexp '^http\..*\.proxy$|^http\.proxy
#39; \ | sed -E 's#(https?://)[^/@ ]+@#\1[redacted]@#'

证书失败要修复信任链,不要绕过验证

  • 确认失败发生在哪个执行位置,以及它使用 Windows、Linux、Java、Node 还是容器信任库。
  • 保存不含认证头的主机名、SNI、验证错误和证书摘要;不要公开内部证书正文。
  • 企业代理重签 TLS 时,从受控渠道取得企业 CA,并只导入需要它的信任库。
  • 使用 curl --cacert 或应用支持的 CA 文件做一次候选验证,再按平台流程安装。
  • 验证错误证书、错误主机名和过期链仍被拒绝,避免把过宽信任当成修复。

Docker daemon、客户端配置和容器是三个代理面

Docker 当前文档分别描述 daemon 访问 registry/节点时的代理,以及写入 Docker client config 后注入新容器和 build 的代理变量。两者不是同一配置;Docker Desktop 还有自己的设置。容器代理值可能以明文出现在配置或镜像元数据中,不要把带凭据的 URL 写入 Dockerfile ENV,也不要为了排障无条件拉取陌生镜像。

  • 先判断失败是 dockerd 拉取镜像、docker CLI 连接 daemon、build step,还是运行中容器的出站请求。
  • 记录 daemon 类型、context、镜像摘要与容器 ID,但不要倾倒完整 inspect 或环境。
  • 若本地已有获准镜像,用 --pull=never 运行一次性探针,避免测试本身依赖待诊断的 registry。
  • 对 build 使用临时 build arg/secret 方案;不要把代理值固化进最终镜像。
  • 修改 daemon 配置需要独立的语法检查、重启窗口和回滚;容器配置只影响新容器/build。

HTTP 状态说明响应层,不等于整条网络健康

观测可确认优先动作
无可信 HTTP 响应DNS、连接、TLS、代理或本地截止仍可能失败保留客户端错误和执行指纹,继续按层定位
401 / 403某个 HTTP 服务拒绝身份或权限确认响应来源,再核对 key、项目、组织与权限
429服务端施加速率、余额或使用限制读取结构化错误与 Retry-After,认证问题不靠重试
5xx上游或中间层返回服务端错误保存请求 ID、时间和有限重试证据
2xx这次请求成功完成继续验证真实操作、其他上下文和时间窗

OpenAI 当前 API 参考要求把 API key 当作秘密并使用 Bearer 认证,建议记录 x-request-id;在没有响应 ID 的超时中,可提供唯一且不含用户数据的 X-Client-Request-Id。当前错误指南把 APIConnectionError 与网络、代理、SSL 或防火墙路径关联,并分别列出 401、429 和 5xx 处理。本文只采用这种分类方法,不把一次匿名请求或状态码写成账号、地区、配额或服务稳定性的结论。

把同一无副作用探针放进证据矩阵

上下文指纹DNS代理/绕过TLSHTTP/应用
Windowshost/user/curl答案与地址族进程与系统来源Windows 信任status/request ID
WSLdistro/kernel/curlresolv.conf 与答案env/autoProxy 结果Linux 信任status/request ID
Remote SSHremote host/user远端解析远端 env/settings远端信任status/request ID
ContainerID/image digest容器解析容器 env/NO_PROXY容器信任status/request ID
npm / Git版本/config 来源运行进程路径工具最终配置工具信任registry/remote 结果

每格写事实、时间和命令,不写“正常”。只比较相同 URL、地址族、代理选择和时间窗;第一个分叉点通常比公网 IP 更接近根因。若所有层都一致而应用仍失败,再核对应用自己的 endpoint、runtime、凭据范围、重试与并发。

一次只改一个配置面,并保留退出动作

  1. 先保存当前文件、来源和作用域;敏感值不进入差异报告。
  2. 优先使用单进程变量、一次性 config 或 host-specific setting 做候选。
  3. 重复正向与反向探针,证明错误证书、错误身份和绕过范围仍被拒绝。
  4. 持久化后启动全新 shell、WSL 实例、远端窗口、容器或服务验证,旧进程不算。
  5. 失败即删除候选并恢复原文件;随后重复基线探针并核对没有残留监听或凭据。

隔离演练验证了路径分叉、分类与不持久化

VPScope 在 mode 0700 的一次性目录启动回环 HTTP 目标、显式代理和自签名 TLS 端点。direct 与 proxy 都返回 200,但代理响应带独立 Via 证据;NO_PROXY 对 127.0.0.1 完成旁路。curl 分别以 6、7、60 和 28 识别 DNS、连接拒绝、TLS 验证和本地超时;401、带两秒 Retry-After 的 429 与 503 保持为 HTTP 结果,超时请求的 client request ID 在目标侧可见。

npm 的用户配置被定向到一次性 npmrc,Git 的 global 配置被定向到一次性文件,设置与删除都通过;真实用户配置未被写入。目标只记录 Authorization 是否存在,不记录值,合成 token 未出现在诊断中。三个监听关闭、临时目录删除通过。主机没有 Docker,因此未运行容器;演练也没有进入 Windows、WSL 或远端主机,没有调用外部 API,不能证明真实代理、证书、凭据、账号、配额或服务可用性。

终端网络不一致排查清单

  • 真正发请求的进程、主机、用户、目录、运行时和工具版本已经定位。
  • Windows、WSL、Remote SSH 与容器的结果分别记录,没有从浏览器成功外推。
  • 配置、DNS、TCP、TLS、HTTP 与应用错误按层分类,连接和总传输都有截止时间。
  • direct、proxy 与 NO_PROXY 使用相同无副作用 URL 比较,绕过范围明确。
  • npmrc、Git、编辑器、daemon 与容器配置来源分别核对,没有假设共享。
  • TLS 验证保持开启;企业 CA 从受控渠道、按需要的信任库安装。
  • 401/403、429、5xx 与无响应没有混为网络正常或账号结论。
  • 日志只含允许字段、状态和请求 ID,不含 secret、认证头、代理凭据或完整环境。
  • 候选先以单进程或一次性配置验证,持久化后由全新进程复验。
  • 回滚已恢复原配置并重复基线探针,临时文件、监听和测试凭据清理完成。

返回知识库