文章

给 Claude 一条干净的线路:一台 VPS 和两台虚拟机

一台美国的 VPS,加上 Mac 上的两台虚拟机:Debian 做透明网关,Windows 只连隔离网,网关一停就断网。Claude 在 Windows 里登录到 VPS 上的 CLIProxyAPI,别的设备通过它来用。

给 Claude 一条干净的线路

用 Claude 一段时间以后,我最在意的已经不是速度,而是出口稳不稳定、环境干不干净。今天在这台机器上登录,明天换一条线路,后天浏览器时区又跟 IP 对不上,这种乱糟糟的状态我很不喜欢。

所以十月下旬花了两个晚上,把这件事做成了一套固定的东西:

  • 一台 Oracle Cloud 的 VPS,在美国西部,作为唯一的出口;
  • Mac 上的 VMware Fusion 跑两台虚拟机:一台 Debian 当网关,一台 Windows 11 专门用来干活;
  • Windows 里不装任何代理软件,它只连着一张“空”的隔离网,唯一能出去的路就是网关;
  • 网关一停,Windows 直接断网,不会悄悄换成我本机的出口;
  • VPS 上再跑一个 CLIProxyAPI:Claude 只在 Windows 里登录一次,别的设备通过它的 API 来用。

这篇把整个过程、配置和踩过的坑都记下来,方便以后重装时照着做,也给有类似想法的朋友参考。

NOTE

本文只记录我自己的网络环境怎么搭,出发点是隐私和稳定。请遵守你所在地的法律法规和各个服务的使用条款。文中所有 IP、端口、UUID、密钥都已替换成 <VPS_IP>、<PORT>、<UUID> 这样的占位符。

整体结构

先看图,后面每一节对应图里的一块。

整体拓扑:Windows → 隔离网 → Debian 网关 → VMware NAT → Mac → 美国 VPS → Claude
图 1 · 整体拓扑

一个数据包从 Windows 出发,大致这样走:

flowchart LR
  W["Windows 11"] -->|"隔离网"| G["Debian 网关<br/>sing-box"]
  G -->|"VLESS-REALITY"| M["Mac<br/>VMware NAT"]
  M -->|"绕过 TUN"| V["VPS<br/>美国西部"]
  V --> C["Claude"]

几台机器分工很明确:

角色 系统 网卡 负责什么
VPS Oracle Cloud · Ubuntu 公网 <VPS_IP> 跑 sing-box 服务端(VLESS-REALITY)和 CLIProxyAPI,是唯一的出口
网关 AIAgentGateway Debian 13 (trixie) enp2s0 NAT + enp10s0 隔离网 发 DHCP、劫持 DNS、把所有流量塞进隧道
工作机 Windows 11 ARM 只有隔离网一张 正常上网、登录账号,不知道代理的存在
宿主机 macOS · VMware Fusion Wi‑Fi / 有线 跑虚拟机;自己的流量走 Egern,但要放过 VPS 那条连接
其他设备 随意 随意 通过 CPA 的 API 用 Claude,自己不登录账号

为什么不直接在 Windows 里开个代理客户端?因为它依赖“我记得开、它也一直没掉”。系统代理不是所有程序都认;客户端一旦崩了或者断线,流量就会从本机出去,而且你多半不会马上发现。把“走不走代理”这件事从 Windows 里拿出去,交给一台专门的网关,Windows 就算想漏也没有路可走。

另外,我不太喜欢 Clash 系那套配置,这次从服务端到网关都用 sing-box,一个配置文件看到底。

一、VPS:美国西部的那台机器

VPS 是 Oracle Cloud 上的一台 Ubuntu,区域在美国西部。节点用的是 233boy 的 sing-box 脚本,默认就是 VLESS-REALITY,装完基本不用管。

VPS · 安装与常用命令
# 安装(以脚本文档为准)
bash <(wget -qO- -o- https://github.com/233boy/sing-box/raw/main/install.sh)
sing-box url # 打印 vless:// 链接,后面喂给网关
sing-box status # 运行状态
sing-box log # 看日志
sing-box port <name> auto # 换端口
sing-box sni <name> <域名> # 换 REALITY 的伪装站点

有几处值得提一下:

  • Oracle 有两层防火墙。控制台里的 Security List(或 NSG)要放行节点端口,机器里的 iptables 也要放行。只开一层,表现就是“端口不通”。我这台机器里的 INPUT 链默认就是放行的,问题一般出在控制台那层。
  • REALITY 伪装站点选一个稳定的大站。脚本会帮你填一个默认值,但最好偶尔在 VPS 上 curl -I 一下它,确认它还能正常访问。
  • 时区和语言。既然出口在美国西部,VPS 我也顺手改成了太平洋时间(America/Los_Angeles),语言用美式英语:
VPS · 时区与 locale
sudo timedatectl set-timezone America/Los_Angeles
sudo apt install -y language-pack-en
sudo localectl set-locale LANG=en_US.UTF-8
timedatectl # Time zone: America/Los_Angeles (PST, -0800)

TIP

timedatectl set-timezone 是永久的,重启也不会变。它只影响这台 VPS 本身,宿主机和已经在跑的 Docker 容器都不会跟着改。容器要用 TZ=America/Los_Angeles 环境变量,或者挂载 /etc/localtime:/etc/localtime:ro,改完重建容器才生效。

拿到 sing-box url 打印的链接,长这样(字段都换成了占位符):

vless 链接结构
vless://<UUID>@<VPS_IP>:<PORT>?encryption=none&flow=xtls-rprx-vision&security=reality&sni=<SNI>&fp=chrome&pbk=<PUBLIC_KEY>&sid=<SHORT_ID>&type=tcp#<NAME>

这一整串就是网关需要的全部信息。它等同于密码,别贴到聊天记录、截图或者公开仓库里。

二、在 Fusion 里搭两张网

这是整套方案的地基。核心只有一句话:Windows 只能看到一张网,这张网里除了网关什么都没有。

VMware Fusion 的网卡分配:网关两张网卡,Windows 只接隔离网
图 2 · 两张网怎么接(示意)

具体做法:

  1. 在 Fusion 的网络设置里新建一张自定义网络,我起名叫 AI Agent Internet;
  2. 把“使用 NAT”“连接这台 Mac”“DHCP”三项全部关掉。这样它就是一根两头都没插东西的网线,子网和网关都由 Debian 自己来发;
  3. 网关虚拟机加两张网卡:第一张用默认的 Share with my Mac(NAT),用来出网;第二张接 AI Agent Internet;
  4. Windows 虚拟机只留一张网卡,接 AI Agent Internet。别图省事再加一张 NAT 当备用,那样 kill switch 就没用了。
虚拟机 网卡 Fusion 网络 系统里的名字 地址
AIAgentGateway NIC 1 Share with my Mac(NAT) enp2s0 172.16.236.x,DHCP
AIAgentGateway NIC 2 AI Agent Internet enp10s0 10.10.10.1/24,静态
Windows 11 NIC 1 AI Agent Internet Ethernet 10.10.10.100–200,由网关 DHCP 分配

IMPORTANT

网卡名每台机器可能不一样(ens160、enp2s0……),不要照抄。下面的脚本会自己判断:有默认路由的那张是上网口,另一张就是私有网口。

三、Debian 网关

网关装的是 Debian 13 (trixie),最小安装,软件源换成了清华的 tuna 镜像。虚拟机文件夹的名字还写着 Debian 12 AI Agent Gateway,实际跑的是 13,别被名字骗了。

3.1 先说几个小坑

  • Debian 默认没有 sudo。sudo: command not found 不是你打错了,先 su - 切 root,或者装上 sudo 再把自己加进 sudo 组。
  • GitHub API 限流。sing-box 官方的 install.sh 要去 GitHub 查最新版本,在国内的 NAT 后面经常被限流,装到一半就失败。后来我改成从 SagerNet 的 apt 源装,稳定很多:
网关 · 从官方 apt 源安装 sing-box
mkdir -p /etc/apt/keyrings
curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
chmod a+r /etc/apt/keyrings/sagernet.asc
cat > /etc/apt/sources.list.d/sagernet.sources <<'EOS'
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
EOS
apt-get update && apt-get install -y sing-box

3.2 一键脚本

网关的配置我写成了一个脚本 setup-gateway.sh,唯一的参数就是上面那串 vless 链接,换节点重跑一遍就行。下面是踩完坑整理过的版本,改过的地方注释里都标了:

setup-gateway.sh
#!/usr/bin/env bash
# Debian + sing-box 透明代理网关(整理版)
# 用法(root):bash setup-gateway.sh 'vless://<UUID>@<VPS_IP>:<PORT>?...'
set -euo pipefail
LINK="${1:?请传入 vless:// 链接}"
CONF="${SB_CONF:-/etc/sing-box/config.json}"
# 上网口 = 有默认路由的网卡;私有网口 = 另一张网卡
WAN=$(ip route show default | awk '{print $5; exit}')
LAN=$(ls /sys/class/net | grep -v -E "^(lo|tun.*|$WAN)$" | head -n1)
[ -n "$WAN" ] && [ -n "$LAN" ] || { echo "没找到两张网卡,请检查虚拟机网卡设置"; exit 1; }
echo "上网口: $WAN 私有网口: $LAN"
LAN_IP=10.10.10.1
if [ -z "${SKIP_SYSTEM:-}" ]; then
apt-get update && apt-get install -y curl dnsmasq python3
# 1. LAN 静态地址
cat > /etc/network/interfaces.d/lan <<C
28 collapsed lines
auto $LAN
iface $LAN inet static
address $LAN_IP/24
C
ifup "$LAN" || ip addr add $LAN_IP/24 dev "$LAN"; ip link set "$LAN" up
# 2. 转发
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-fwd.conf && sysctl --system >/dev/null
# 3. DHCP(只发地址,DNS 交给 sing-box 劫持)
cat > /etc/dnsmasq.d/lan.conf <<C
interface=$LAN
bind-interfaces
port=0
dhcp-range=10.10.10.100,10.10.10.200,12h
dhcp-option=3,$LAN_IP
dhcp-option=6,$LAN_IP
C
systemctl restart dnsmasq && systemctl enable dnsmasq
# 4. 安装 sing-box:走 SagerNet 官方 apt 源(GitHub API 限流时 install.sh 会失败)
if ! command -v sing-box >/dev/null; then
mkdir -p /etc/apt/keyrings
curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
chmod a+r /etc/apt/keyrings/sagernet.asc
printf 'Types: deb\nURIs: https://deb.sagernet.org/\nSuites: *\nComponents: *\nEnabled: yes\nSigned-By: /etc/apt/keyrings/sagernet.asc\n' \
> /etc/apt/sources.list.d/sagernet.sources
apt-get update && apt-get install -y sing-box
fi
fi
# 5. 解析 vless 链接生成配置
mkdir -p "$(dirname "$CONF")"
python3 - "$LINK" "$WAN" "$CONF" <<'PY'
import sys, json, urllib.parse as u
link, wan, conf = sys.argv[1:4]
l = u.urlparse(link); q = dict(u.parse_qsl(l.query))
node = {"type": "vless", "tag": "my-node", "server": l.hostname, "server_port": l.port or 443,
"uuid": u.unquote(l.username), "flow": q.get("flow", ""),
"tls": {"enabled": True, "server_name": q.get("sni", ""),
"utls": {"enabled": True, "fingerprint": q.get("fp", "chrome")},
"reality": {"enabled": True, "public_key": q.get("pbk", ""), "short_id": q.get("sid", "")}}}
cfg = {
"log": {"level": "warn"},
"dns": {"servers": [{"type": "https", "tag": "remote", "server": "1.1.1.1", "detour": "my-node"}]},
"inbounds": [{"type": "tun", "tag": "tun-in", "address": ["172.19.0.1/30"],
"auto_route": True, "auto_redirect": True, "strict_route": True}],
"outbounds": [node, {"type": "direct", "tag": "direct"}],
"route": {
"rules": [
{"action": "sniff"},
{"protocol": "dns", "action": "hijack-dns"},
# 连节点本身的流量必须直连,否则会“自己代理自己”
{"ip_cidr": [f"{l.hostname}/32"], "outbound": "direct"},
],
"final": "my-node",
# 这台 VM 上 auto_detect_interface 会报 missing default interface,直接写死上网口
"default_interface": wan,
},
}
json.dump(cfg, open(conf, "w"), indent=2)
PY
sing-box check -c "$CONF"
[ -n "${SKIP_SYSTEM:-}" ] && exit 0
systemctl enable sing-box && systemctl restart sing-box
echo "完成:Windows 接到私有网络后会自动拿到 10.10.10.x 地址。"
echo "检查状态: systemctl status sing-box ; 换节点: 重新运行本脚本并传入新链接"

用起来就一行:

su -
bash /home/hychiiu/setup-gateway.sh 'vless://<UUID>@<VPS_IP>:<PORT>?...'

TIP

想先看看生成的配置对不对,可以加两个环境变量做一次“干跑”,只生成配置、跑 sing-box check,不动系统: SKIP_SYSTEM=1 SB_CONF=/tmp/test.json bash setup-gateway.sh '<链接>'

3.3 配置要点

脚本第 5 步生成的就是全部配置,记住四点就够了:

  • TUN 接管一切:auto_route + auto_redirect 把本机和转发进来的流量都收进 tun0,strict_route 不留缝;
  • DNS 也进隧道:hijack-dns 截下 Windows 的 53 端口请求,再经 my-node 用 DoH 解析;
  • 节点 IP 直连,不然会自己代理自己;
  • 不用手写 NAT:dnsmasq 只发地址(port=0),转发靠 ip_forward,nftables 规则 sing-box 自己会配。

3.4 systemd

apt 装的 sing-box 自带 systemd 服务,读 /etc/sing-box/ 下的配置,开机自启:

网关 · 日常看状态
systemctl is-active sing-box dnsmasq # 两个都该是 active
systemctl status sing-box --no-pager -l
journalctl -u sing-box -n 50 --no-pager
ip -br a | grep -E 'tun|enp'
curl -s https://api.ipify.org; echo # 应该是 <VPS_IP>
网关和 Windows 各自看到的网络
图 3 · 网关的路由表和 Windows 的 ipconfig(真实输出,有删节)

3.5 Kill switch

这套设计最让我安心的就是这一点:不需要额外的规则,kill switch 是网络拓扑本身给的。

网关在线与关机时的对比
图 4 · 网关一停,Windows 就断网

Windows 只有一张网卡,那张网卡所在的网里没有 NAT、没有宿主机、没有 DHCP 服务器,只有网关。所以:

  • 网关虚拟机关机或挂起:Windows 没有任何路由能出去,直接断网;
  • 网关开着、sing-box 正常:所有流量进 TUN,从美国那台 VPS 出去;
  • 网关开着、sing-box 挂了:转发过去的包源地址还是 10.10.10.x,网关上没有做 NAT,VMware 的 NAT 不认这个网段,回程的包回不来,多半也是出不去。

第三种情况我是靠“没有 NAT 规则”来兜底的,算不上严格。想再保险一点,可以加一条显式的规则,禁止私有网口直接转发到上网口(这条我自己还没长期跑过,仅供参考):

可选 · /etc/killswitch.nft
table inet killswitch {
chain forward {
type filter hook forward priority 0; policy accept;
iifname "enp10s0" oifname "enp2s0" drop
}
}

用 nft -f /etc/killswitch.nft 加载。TUN 接管的流量出口是 tun0,不受这条影响,它只在 sing-box 不在的时候起作用。

WARNING

Debian 自带的 /etc/nftables.conf 第一行是 flush ruleset。如果你把规则写进那个文件再重载,sing-box 自己建的表也会被一起清掉,重载完记得 systemctl restart sing-box。

四、Windows:让它看起来就“在美国”

Windows 里什么代理都不用装。插上 AI Agent Internet 这根网线,开机就能从网关拿到 10.10.10.1xx 的地址,网关和 DNS 都是 10.10.10.1。

剩下要做的是让系统环境和出口对得上。出口在美国西部,Windows 的时区就设成太平洋时间:

出口位置、Windows 时区、语言三者的对应
图 5 · 出口在哪,时间就在哪
Windows · 时区
tzutil /g # 查看当前时区 ID
tzutil /s "Pacific Standard Time" # (UTC-08:00) Pacific Time (US & Canada),夏令时自动切换
Get-TimeZone

语言基本不用动。系统本来就是英文版,显示语言保持 English (United States),正好和出口对得上,只在输入法里加了中文,平时打字方便:

Windows · 语言与输入法
Get-WinSystemLocale
Get-Culture
Get-WinUserLanguageList
# 保持英文界面,追加中文输入
$list = Get-WinUserLanguageList
$list.Add("zh-CN")
Set-WinUserLanguageList $list -Force

WARNING

设置里“自动设置时区”建议关掉。它会按位置服务去猜时区,猜出来的结果不一定和出口一致,哪天自己变回 UTC+8 都不知道。

五、CLIProxyAPI:在 Windows 里登录一次,别处都能用

这一步上一版漏写了。环境是干净了,但我不可能所有活都在虚拟机里干:Mac 终端里的 Claude Code、别的电脑,也想用同一个账号。要是每台设备各自登录一遍,又回到了“今天这里、明天那里”的老样子。

所以我在同一台 VPS 上又跑了一个 CLIProxyAPI(下面简称 CPA)。它能通过 OAuth 登录 Claude Code 的账号,再把它包成 Anthropic / OpenAI 兼容的 API。分工是这样的:

  • 登录只做一次,在 Windows 里做。走的是那条干净的线路,浏览器、时区、出口都对得上;
  • 凭证留在 VPS 上。CPA 把 OAuth 凭证存在自己的 auth 目录里,过期会自己刷新;
  • 别的设备只拿一个 API Key。它们请求 CPA,CPA 再从 VPS 去请求 Claude,所以 Claude 看到的出口还是这台 VPS,和 Windows 是同一个。
CPA:Windows 登录一次,其他设备用 API Key 调用
图 6 · 登录一次,到处调用

5.1 部署

我用 Docker Compose 跑官方镜像 eceasy/cli-proxy-api。端口只绑在本机,对外交给 Caddy 反代成 HTTPS:

VPS · docker-compose.yml(节选)
services:
cli-proxy-api:
image: eceasy/cli-proxy-api:latest
restart: unless-stopped
ports:
- "127.0.0.1:<PORT>:<PORT>" # 只给本机的 Caddy 用
environment:
- TZ=America/Los_Angeles
volumes:
- ./cli-proxy-api/config.yaml:/CLIProxyAPI/config.yaml
- ./cli-proxy-api/auth:/root/.cli-proxy-api # OAuth 凭证在这里
- /etc/localtime:/etc/localtime:ro

config.yaml 从仓库里的 config.example.yaml 复制一份,改这几项就能跑:

VPS · config.yaml(节选,v8 写法)
config-version: 8
server:
port: <PORT>
management:
allow-remote: true # 要在别的机器上开管理面板才需要
secret-key: "<MANAGEMENT_KEY>"
access:
api-keys:
- "<API_KEY>" # 给客户端用的 key,不是 Claude 的
VPS · /etc/caddy/Caddyfile
<你的域名> {
reverse_proxy 127.0.0.1:<PORT>
}

docker compose up -d 起来以后,docker compose logs -f cli-proxy-api 看一眼没有报错就行。

TIP

这里有三个小坑:

  1. api-keys 还是示例里的占位值时,CPA 会进安全模式,API 全部关掉,换成自己的 key 才能用;
  2. 容器时区用 TZ 加挂载 /etc/localtime 就够了,别再去挂 /etc/timezone,会报 not a directory;
  3. Caddy 配置里如果写了日志文件,/var/log/caddy 目录要先建好并 chown caddy:caddy,不然 Caddy 起不来。

5.2 在 Windows 里登录

这是整套环境里唯一需要“人”在场的一步,所以放在最干净的那台机器上做:

  1. 在 Windows 的浏览器里打开管理面板。CPA 自带一个,地址是 https://<你的域名>/management.html,用上面的 secret-key 登录(我用的是社区的 cpa-manager-plus,用法差不多);
  2. 在面板里发起 Claude 的 OAuth 登录,拿到授权链接,在同一个浏览器里打开,登录账号并授权;
  3. 授权完浏览器会跳到一个 localhost 的回调地址。CPA 不在 Windows 上,这个页面多半打不开,不用管,把地址栏里的完整 URL 复制下来粘回面板提交就行;
  4. 面板里出现这个账号、状态正常,就登录好了。

5.3 在别的设备上用

Claude Code 只要两个环境变量:

任意设备 · Claude Code
export ANTHROPIC_BASE_URL="https://<你的域名>"
export ANTHROPIC_AUTH_TOKEN="<API_KEY>"
claude

OpenAI 兼容的客户端,Base URL 填 https://<你的域名>/v1,Key 填同一个。先用一条 curl 确认通了:

curl -s https://<你的域名>/v1/models -H "Authorization: Bearer <API_KEY>"

CAUTION

CPA 背后是你自己的账号。API Key 和管理密钥都要够长,别外传;这套只给自己的设备用,别拿去分享给别人,既不安全,也违反服务条款。

六、Mac 这边:TUN 和网关抢路

这一节是我花时间最多的地方。

我的 Mac 平时开着 Egern,TUN 模式(vif_only: true),还劫持了全部 DNS(hijack_dns: *:53)。问题在于,网关通过 VMware NAT 发出去的流量,在 Mac 看来就是一台普通的本机流量,于是也被 Egern 的 TUN 收了进去,转给了别的节点。

Mac 上的 TUN 截走了网关连 VPS 的流量
图 7 · 绕过之前和之后

这会导致两个现象:

  1. 网关自己解析不了域名。Egern 劫持了 53 端口,VMware NAT 自带的 DNS(172.16.236.2)跟着失效。我在网关的 /etc/resolv.conf.head 里写死了一个公共 DNS 绕开它。顺带一提,看到解析结果是 198.18.x.x 这种地址,基本就是 fake-ip 在作怪。
  2. REALITY 握手失败。VLESS 连接被包了一层又转出去,到了 VPS 那头,服务端日志里全是 REALITY: processed invalid connection。

最直接的验证是把 Egern 整个关掉:一关,网关马上就通了,原因也就坐实了。但我不想为了虚拟机让 Mac 裸奔,所以长期的办法是让 VPS 那个 IP 完全不进 Egern 的隧道,直接走物理网卡:

Egern 配置(片段)
bypass_tunnel_proxy:
- "<VPS_IP>/32"
vif_excluded_routes:
- "<VPS_IP>/32"

bypass_tunnel_proxy 让这个地址不走代理,vif_excluded_routes 才是真正把它从虚拟网卡的路由里踢出去的那一项。换别的客户端也一样:给 VPS 的 IP 加一条 DIRECT,并且确认它的路由不经过 utun。

CAUTION

我在这里栽过两次:

  1. 改完配置只点了“重新加载”,路由没刷新。要断开再连接一次才生效。
  2. 我改的是自己保存的那份配置,Egern 实际加载的却是 ~/Library/Group Containers/…/Local/ 下面的一份旧副本,里面根本没有这条规则。改之前先确认它真正在用的是哪个文件。

验证方法很简单:在 Mac 上 route -n get <VPS_IP>,看出口网卡是 en0 之类的物理网卡,而不是 utun*。

七、让网关在后台安静地跑

网关不需要图形界面,每次开 Fusion 还多一个窗口挺烦。Fusion 自带 vmrun,可以无窗口启动:

Mac · ~/.zshrc
VMRUN="/Applications/VMware Fusion.app/Contents/Library/vmrun"
GW_VMX="$HOME/Virtual Machines.localized/Debian 12 AI Agent Gateway.vmwarevm/Debian 12 AI Agent Gateway.vmx"
alias gw-up='"$VMRUN" -T fusion start "$GW_VMX" nogui'
alias gw-down='"$VMRUN" -T fusion stop "$GW_VMX" soft'
alias gw-ip='"$VMRUN" -T fusion getGuestIPAddress "$GW_VMX" -wait'
alias vms='"$VMRUN" list'

平时 gw-up 一下,等十几秒再开 Windows 就行。要进网关就 ssh hychiiu@<网关的 NAT 地址>,gw-ip 能查到它在 172.16.236.x 上的地址。

TIP

启动顺序是先网关、后 Windows。反过来也没事,只是 Windows 一开始拿不到 DHCP 地址,等网关起来后在 Windows 里 ipconfig /renew 一下就好。

八、检查清单

换节点、改配置之后,按这张清单从里到外过一遍:

八项检查
图 8 · 检查清单
# 检查什么 在哪做 怎么算通过
1 网关出口 网关 curl -s https://api.ipify.org 返回 <VPS_IP>
2 Windows 的地址 Windows ipconfig /all:10.10.10.1xx,网关和 DNS 都是 10.10.10.1
3 浏览器里的 IP Windows 浏览器 任意 IP 查询页显示美国,位置和 VPS 的区域一致
4 DNS 泄漏 Windows 浏览器 DNS leak test 的扩展测试里看不到国内运营商的解析服务器
5 WebRTC Windows 浏览器 WebRTC 泄漏测试里没有真实公网 IP
6 时区 Windows tzutil /g 是 Pacific Standard Time,浏览器 Intl.DateTimeFormat().resolvedOptions().timeZone 是 America/Los_Angeles
7 Kill switch Mac + Windows gw-down 以后,Windows 里 ping 8.8.8.8 不通
8 CPA 任意设备 curl https://<你的域名>/v1/models 带上 Key 能返回模型列表

第 6 项可以直接在浏览器控制台里看:

浏览器控制台
Intl.DateTimeFormat().resolvedOptions().timeZone // "America/Los_Angeles"
new Date().getTimezoneOffset() // 480,也就是 UTC-8(夏令时是 420)

九、关于 Claude 的几点

环境搭好以后,Claude 账号的登录只在这台 Windows 里做,包括授权给 CPA 的那一次;别的设备只用 CPA 的 Key,不直接登录。几点体会:

  • 出口固定比什么都重要。同一个账号,今天一个国家,明天另一个国家,比用哪家 VPS 更容易出问题。现在不管是 Windows 的浏览器,还是经 CPA 发出去的请求,都只有美国这一个出口。
  • 时区、语言和出口对齐。这是第四节做的事。不用做到偏执,但别出现“IP 在美国、系统时间是北京时间”这种一眼就能看出来的矛盾。
  • Mac 上的 Claude Code 走 CPA。它不经过虚拟机网关,靠 ANTHROPIC_BASE_URL 指向自己的 CPA。以前用第三方中转时踩到过一个坑:有些中转只接受官方 CLI 发出的请求,Claude Code Desktop 发的请求会被拒,表现为 preflight 阶段提示“请升级 Claude Code”。这不是版本问题,换回 CLI 就好。Desktop 怎么接自己的服务我还在折腾,以后有结论再写。

十、我踩过的坑

把这段时间遇到的问题按“症状 → 原因 → 解决”整理一下,下次再遇到直接查表:

症状 原因 解决
install.sh 装 sing-box 半路失败 GitHub API 限流 改用 deb.sagernet.org 的 apt 源
sing-box 起不来,报 missing default interface 这台 VM 上 auto_detect_interface 识别不到 route.default_interface 写成 enp2s0
sudo: command not found Debian 最小安装不带 sudo su - 切 root
网关 ping 8.8.8.8 通,域名解析失败 Mac 上的 Egern 劫持了 DNS,VMware NAT 的 DNS 失效 /etc/resolv.conf.head 写一个公共 DNS
解析结果是 198.18.x.x Mac 那边的 fake-ip 同上,并让 VPS IP 绕过 TUN
日志里一堆 outbound/vless[my-node]: EOF、context deadline exceeded,端口却是通的 连节点本身的流量也走了 vless,自己代理自己 路由里给 <VPS_IP>/32 加 direct
VPS 日志 REALITY: processed invalid connection Mac 的 TUN 把网关的 VLESS 流量又转了一道 Egern 里 bypass_tunnel_proxy + vif_excluded_routes
改了 Egern 配置还是不行 只重新加载,没断开重连;或者改错了文件 断开再连接;确认实际加载的配置文件
CPA 的接口全部不可用,提示安全模式 api-keys 还是示例值 换成自己的 key
Caddy 起不来 日志目录 /var/log/caddy 不存在 建目录并 chown caddy:caddy
时区改了,担心影响握手 不会 TLS / REALITY 看的是 UTC 时钟准不准,跟时区设成哪里无关
那次断网的排查过程
图 9 · 服务都在,就是出不去(真实输出,有删节)

那次断网的排查过程很典型:sing-box 和 dnsmasq 都是 active,到节点的 TCP 也是通的,就是隧道建不起来。我先停掉 sing-box 测直连,确认 VMware NAT 本身没问题;再看 VPS 那头的日志,发现握手被判无效;最后才追到 Mac 上的 TUN。从里往外,一层一层排除,比一上来就换端口、换节点有效得多。

TIP

排查时最有用的一条命令:先在网关上停掉 sing-box,curl -s https://api.ipify.org 看直连能不能通。能通,就说明问题在隧道或节点;不通,就是 Fusion、Mac 或者上游的问题。测完记得 systemctl start sing-box 拉回来,否则 kill switch 的保护也没了。

写在最后

回头看,这套东西真正起作用的是两个决定:

  1. 把“走不走代理”从工作机里拿出去。Windows 不知道代理的存在,也就没有“代理掉了”这回事;
  2. 让断网成为默认状态。隔离网里没有别的出口,网关不在就什么都不通。安全不靠我记得开代理,靠网线本身。

剩下的,sing-box、REALITY、时区,都是细节,换成别的工具也一样成立。后来补上的 CPA 则把“在干净的环境里登录一次”变成了“在哪儿都能用”,出口却还是同一个。要换节点,在 VPS 上 sing-box url 拿到新链接,网关上重跑一遍脚本,五分钟就好。

如果你也在搭类似的环境,希望这篇能帮你少踩几个坑。有问题可以在下面留言。

#network#sing-box#vmware#claude
5383 字 · 2026·11·11

评论0

还没有评论。

在 GitHub 上留言