从不稳定的代理节点到自建 CN2 GIA 出口:搬瓦工 + Xray Reality + Clash Verge 全流程
一个下午,从「决定自建」到「Claude Code、GitHub、Maven Central 全部走新出口」,链路彻底切换完成。最终状态是:一台洛杉矶 CN2 GIA 线路的 VPS,跑 Xray 的 VLESS + Reality,本机 Clash Verge 规则分流,国内直连、海外走代理;年费约 $170,日常零维护,每季度登录一次升个级。
这篇按操作顺序拆解整个过程:选型 → 购机与加固 → 服务端部署 → 客户端接入 → 验证与切换。每个阶段给出要做什么、怎么验证做对了,以及实际撞到的坑——其中一个(Reality 握手失败)我翻遍搜索引擎没找到现成解释,最后是靠服务端的详细日志一行行读出来的。如果你正带着 handshake did not complete successfully 搜到这里,可以直接跳到阶段三的踩坑。
全局:一条自建出口由哪几层组成
本机应用(终端 / 浏览器 / Claude Code)
↓ 环境变量 HTTPS_PROXY 或系统代理 → 127.0.0.1:7897
Clash Verge(mihomo 内核)
↓ 规则分流:国内域名/IP 直连,其余走 PROXY 组
↓ VLESS + Reality,TCP 443
海外 VPS(Xray 服务端)
↓ 解密后以自身 IP 直连目标
GitHub / Anthropic / Maven Central ...
动手前要想清楚的三件事:
- 稳定性主要由线路决定,而不是软件。国内到美西的普通国际线路晚高峰绕路丢包是常态,代理节点「越来越不稳」多半是这个原因。中国电信 CN2 GIA 这类优化线路直连回程,晚高峰依然稳,这是本文选型的出发点。
- 协议只需要一个主协议。VLESS + Reality 走 TCP,不需要域名和证书,TLS 握手伪装成访问某个大站,是目前抗识别最强的主流方案。低丢包线路上不需要再叠 UDP 类协议。
- 客户端不开 TUN。TUN 会接管全部系统流量,对公司内网工具、安全软件都是隐患;开发用途靠环境变量和系统代理精确控制哪些进程走代理就够了。
阶段一:选型
这一阶段的目标:定下线路、服务商和协议,避免在错误的基础上折腾。
线路:优化线路 vs 普通国际线路
| 类型 | 代表 | 月费 | 特点 |
|---|---|---|---|
| 普通国际线路 | Vultr / DigitalOcean / AWS Lightsail 洛杉矶 | $5–6 | 白天还行,晚高峰绕路、丢包明显 |
| 中国优化线路(CN2 GIA / CMIN2 / 9929) | 搬瓦工 DC6、DMIT LAX Pro | $10–20 | 三网直连回程,晚高峰依然稳,对流式长连接友好 |
区域选美西洛杉矶:延迟 150 ms 左右对开发工具无感,各类海外服务全部可用;日本延迟更低,但国内到日本的线路晚高峰反而更差。
服务商:搬瓦工 vs DMIT
两家都是 CN2 GIA 线路的老牌选手,实测(2026-09)对比:
| 套餐 | 配置 | 价格 | 差异点 |
|---|---|---|---|
| 搬瓦工 CN2 GIA-E 20G | 1 核 / 1 GB / 1 TB 月流量 / 2.5 Gbps | $49.99/季、$169.99/年 | 后台免费自助迁机房(洛杉矶 DC6/DC9、日本软银等 10+ 个),长期有货 |
| DMIT LAX.AS3.Pro.TINY | 1 核 / 2 GB / 1 TB / 1 Gbps | $10.90/月 | 可月付、每月免费换一次 IP;小套餐常断货 |
我选了搬瓦工年付:有货、后台能迁机房、年付省心。DMIT 的差异化在月付和免费换 IP,断货时是很好的替补。
这个套餐(CN2 GIA-E 20G)值得多说两句,因为它的规格看起来寒酸,值钱的部分在线路上:
- 硬件:2 核共享 Xeon、1 GB 内存、20 GB SSD、1 TB 月流量、2.5 Gbps 端口。Xray 常驻约 30 MB 内存、负载接近零,开发用途一个月流量 20–50 GB,手机加上也用不完
- 线路:电信 CN2 GIA(AS4809)双向直连,联通走电信提供的企业级传输通道,移动走 CMIN2 回程,三网都做了优化——家里宽带和手机流量是哪家运营商都一样;和 Google 直接对等互联
- 机房迁移:可在 KiwiVM 后台免费迁到 10+ 个机房(DC9 同为 CN2 GIA,另有日本软银、荷兰等),迁移会换 IP,这是晚高峰变差或 IP 被封时的零成本手段
- 面板:装系统、快照、自动备份、反向 DNS、网页 SSH 都在 KiwiVM 里;严格 self-managed,官方只保 99.95% 在线率
- 不是什么:没有 SLA 赔付(那是贵一档的 ECOMMERCE SLA 系列),共享 CPU 不适合跑编译或业务;续费保价
线路和大规格版本完全相同,作为个人出口,最低档刚好够。
提示:服务商官网的价格和库存变化很快,下单前以购物车页为准。DMIT 官网套了 Cloudflare 人机验证,用代理访问经常过不去,直连即可。
协议:VLESS + Reality
- 主协议 VLESS + Reality(Xray):TCP,不需要域名和证书,流量伪装成访问某个大站的 TLS 握手
- 备用 Hysteria2:基于 QUIC,抗丢包强,但要自签证书,部分运营商对 UDP 做 QoS,只作第二入口
- 服务端直接写 Xray 的 JSON,不装管理面板——面板多一个攻击面,几十行配置自己写更可控
✅ 本阶段验证
一句话能说清「为什么是这条线路、这家服务商、这个协议」,就可以进入下一步。选型没定就动手,后面每一步都可能返工。
阶段二:购机与系统加固
这一阶段的目标:拿到一台只开 SSH 和 443、只允许密钥登录的干净 Debian。
1. 下单
搬瓦工的订单页有几个细节:
- 注册信息填真实地址,国家选 China,并且提交订单前关掉代理——页面底部会显示你当前 IP,服务商用它做欺诈判定,「国家填中国、IP 在海外」是被卡人工审核的最常见原因
- 订单页的「Public SSH keys」直接贴本机公钥(
cat ~/.ssh/id_ed25519.pub),装系统时会自动写入 root,省掉后面的密钥配置 - 支持支付宝
2. 重装系统
购买后 KiwiVM 面板默认装的是 AlmaLinux,走 Install new OS 重装成 Debian(当前稳定版 Debian 13)。重装前要先在 Main controls 里 Stop 虚拟机,否则按钮会报 Unable to reload a running VM。重装页会显示 SSH 端口和临时 root 密码——已经贴了公钥的话密码用不上。
3. 加固
密钥登录通了之后,一次性做完三件事:
# 只允许密钥登录
cat > /etc/ssh/sshd_config.d/10-hardening.conf <<EOF
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
EOF
sshd -t && systemctl reload ssh
# 防火墙只放 SSH 和 443
apt-get install -y nftables
cat > /etc/nftables.conf <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
tcp dport { 22, 443 } accept
udp dport 443 accept
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept; }
}
EOF
nft -f /etc/nftables.conf && systemctl enable --now nftables
# BBR
printf 'net.core.default_qdisc=fq\nnet.ipv4.tcp_congestion_control=bbr\n' > /etc/sysctl.d/99-bbr.conf
sysctl -p /etc/sysctl.d/99-bbr.conf
如果 SSH 端口不是 22,nftables 里对应改掉——先改防火墙再 reload sshd,顺序反了会把自己锁在外面。
✅ 本阶段验证
- 新开一个终端用密钥能登录,
ssh -o PubkeyAuthentication=no应被拒绝 sysctl net.ipv4.tcp_congestion_control输出bbrnft list ruleset里 input 链 policy 是 drop,只放了 22 和 443
阶段三:服务端部署 Xray
这一阶段的目标:443 端口跑起 VLESS + Reality,并且先用 Xray 自己当客户端在服务器本机验证通过,再去碰任何第三方客户端。
1. 安装并生成参数
bash -c "$(curl -fsSL https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install -u root
xray uuid # 客户端 UUID
xray x25519 # 输出 PrivateKey(服务端)和 Password/PublicKey(客户端)
openssl rand -hex 4 # shortId
新版 Xray 把 x25519 输出里的「Public key」改叫了「Password」,客户端配置里填的还是它。
2. 写配置
/usr/local/etc/xray/config.json:
{
"log": { "loglevel": "warning" },
"inbounds": [{
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [{ "id": "<UUID>", "flow": "xtls-rprx-vision" }],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "dl.google.com:443",
"xver": 0,
"serverNames": ["dl.google.com"],
"privateKey": "<PrivateKey>",
"shortIds": ["<shortId>"]
}
},
"sniffing": { "enabled": true, "destOverride": ["http", "tls", "quic"] }
}],
"outbounds": [
{ "protocol": "freedom", "tag": "direct" },
{ "protocol": "blackhole", "tag": "block" }
]
}
xray run -test -config /usr/local/etc/xray/config.json # Configuration OK
systemctl enable --now xray
ss -ltnp | grep ':443 '
✅ 本阶段验证:先用 Xray 自己连自己
第三方客户端连不上时,你无法区分是服务端配置错了还是客户端兼容问题。所以在服务器上再起一个 Xray 实例当客户端,指向 127.0.0.1:443:
{
"inbounds": [{ "listen": "127.0.0.1", "port": 10808, "protocol": "socks" }],
"outbounds": [{
"protocol": "vless",
"settings": { "vnext": [{ "address": "127.0.0.1", "port": 443,
"users": [{ "id": "<UUID>", "encryption": "none", "flow": "xtls-rprx-vision" }] }] },
"streamSettings": { "network": "tcp", "security": "reality",
"realitySettings": { "serverName": "dl.google.com", "fingerprint": "chrome",
"publicKey": "<Password/PublicKey>", "shortId": "<shortId>" } }
}]
}
xray run -config /tmp/client.json &
curl --socks5-hostname 127.0.0.1:10808 https://ipinfo.io/ip # 应输出 VPS 自己的 IP
这条自测通过,服务端就是对的;后面客户端连不上只需要查客户端。
踩坑:伪装目标的证书链太大,握手永远完不成
我最初把 dest 设成 www.microsoft.com:443——它 TLS 1.3 + HTTP/2 都支持,是很多教程里的常见选择。结果所有连接被服务端直接断开,客户端报 connect error: EOF,服务端日志只有一行:
REALITY: processed invalid connection from x.x.x.x: handshake did not complete successfully
这句话极具误导性——它让人去怀疑密钥、shortId、时间差,而这些全都核对无误,连服务器本机的 Xray 自测都失败。把 realitySettings.show 打开后,详细日志才把真相摆出来:
hs.c.ClientShortId: [...] ← shortId 匹配
hs.c.conn == conn: true ← 认证通过
len(s2cSaved): 4096 Server Hello: 1215
len(s2cSaved): 2881 Change Cipher Spec: 6
len(s2cSaved): 2875 Encrypted Extensions: 41
len(s2cSaved): 2834 Certificate: 8273 ← 证书消息 8273 字节,缓冲只剩 2834
hs.c.isHandshakeComplete.Load(): false
根因:Reality 服务端会先和伪装目标做一次真实握手、把对方的 ServerHello / Certificate 等消息读进一个 4096 字节的缓冲,用来模仿目标的握手形态。www.microsoft.com 的证书链有 8 KB 多,一条 Certificate 消息就装不下,握手状态永远到不了 complete,服务端便把这条已经认证通过的连接当成非法连接处理。
修复:换一个证书链小的伪装目标。dl.google.com 的证书链约 2–3 KB,改完立刻通。挑 dest 时除了「支持 TLS 1.3 + H2、非国内站」,再加一条:
# 在 VPS 上量一下候选目标的证书链大小
echo | openssl s_client -connect dl.google.com:443 -servername dl.google.com 2>/dev/null \
| openssl x509 -outform DER | wc -c
叶子证书本身不超过 2 KB、整条链不超过 4 KB 的,才是安全的选择。
阶段四:客户端接入 Clash Verge
这一阶段的目标:本机 Clash Verge 加载新节点,规则分流,混合端口 7897 起来。
1. 写 profile
Clash Verge(mihomo 内核)原生支持 VLESS + Reality,新建一个本地 profile:
mixed-port: 7897
allow-lan: false
mode: rule
ipv6: false
dns:
enable: true
nameserver:
- https://223.5.5.5/dns-query
- https://120.53.53.53/dns-query
proxies:
- name: MY-LA
type: vless
server: <VPS IP>
port: 443
uuid: <UUID>
network: tcp
tls: true
udp: true
flow: xtls-rprx-vision
servername: dl.google.com
client-fingerprint: chrome
reality-opts:
public-key: <Password/PublicKey>
short-id: <shortId>
proxy-groups:
- name: PROXY
type: select
proxies: [MY-LA, DIRECT]
rules:
- GEOSITE,private,DIRECT
- GEOIP,LAN,DIRECT,no-resolve
- GEOSITE,cn,DIRECT
- GEOIP,CN,DIRECT,no-resolve
- MATCH,PROXY
servername 必须和服务端 serverNames 一致;服务端换了伪装目标,这里要同步改。
规则是怎么分流的
思路只有一句话:能证明是”国内”的直连,其余一律走代理。匹配从上到下,命中即停:
| 顺序 | 规则 | 出口 | 作用 |
|---|---|---|---|
| 1 | GEOSITE,private | DIRECT | localhost、.local 等本地域名 |
| 2 | GEOIP,LAN,no-resolve | DIRECT | 局域网 IP |
| 3 | GEOSITE,cn | DIRECT | 域名在中国站点列表里(.cn 后缀、淘宝、腾讯、字节等几十万条,子域名自动包含) |
| 4 | GEOIP,CN,no-resolve | DIRECT | 目标 IP 属于中国大陆 IP 段——兜域名规则没收录的国内小站 |
| 5 | MATCH | PROXY | 以上都没命中 → 走 VPS |
两个数据源都不用自己维护:geosite 来自 v2fly 社区的 domain-list-community,geoip 来自 MaxMind/ipip 的国家 IP 段,随客户端版本更新。
no-resolve 这个标记值得单独理解:它的含义是「连接目标是域名时跳过本条、不去解析 IP」。浏览器和 CLI 经 HTTP 代理时传的都是域名,所以 GEOIP 规则不会为了判定去做一次 DNS 解析——既省一次解析,也避免了「国内 DNS 把海外域名解析到国内 CDN 节点、被误判直连」这类事故。
这直接关系到一个很多人关心的问题:Claude、ChatGPT 这类对代理敏感的服务,会不会有部分请求漏到直连? 在这套规则下不会。它们的域名(claude.ai、api.anthropic.com、chatgpt.com、openai.com)不在 geosite:cn 里,IP 在 Cloudflare 和 AWS 美国段,域名连接又因 no-resolve 不走 GEOIP 判定,只能落到兜底走代理。可以从 Clash 的日志里取证:
[TCP] 127.0.0.1:50912 --> claude.ai:443 match Match using PROXY[MY-LA]
[TCP] 127.0.0.1:50915 --> api.anthropic.com:443 match Match using PROXY[MY-LA]
[TCP] 127.0.0.1:50921 --> chatgpt.com:443 match Match using PROXY[MY-LA]
真正会触发这类服务风控的是另外两件事:同一账号在多个设备上用不同出口(短时间内 IP 在不同国家/城市之间跳),以及手机没开代理时直接打开 App(请求从国内运营商 IP 打到对方)。自建出口天然解决前者——所有设备都用同一台 VPS、同一个出口 IP,从服务方视角你就是一个固定在洛杉矶的用户;后者靠手机端代理常开 + 开机自启来规避。
边界情况:国内公司用海外域名 + 海外 CDN 的站点会走代理(通常反而更快);海外公司在国内有 CDN 节点的(Apple、微软部分域名)会命中 GEOIP,CN 直连,都属正常。极少数走错的,在 Clash「连接」页看它匹配了哪条,加一行 DOMAIN-SUFFIX,xxx.com,DIRECT 做例外即可。
2. 在界面上做两件事
- 首页「代理模式」选规则
- 设置里打开开机自启
踩坑:Verge 的模式只认界面操作
我一开始图省事,用 mihomo 的控制接口 PATCH /configs {"mode":"rule"} 切了模式,测试全通;但 Verge 自己的状态文件里记录的仍是 global,它下次重启会把模式写回去。GLOBAL 组默认指向 DIRECT,等于代理静默失效。模式、开机自启这类状态,一定要在 Verge 界面上点一次让它自己持久化;如果界面已经显示「规则」高亮,先点「全局」再点回「规则」,逼它写一次。
✅ 本阶段验证
P=http://127.0.0.1:7897
curl -x $P https://ipinfo.io/json # ip/org 应是 VPS
curl -x $P -o /dev/null -w '%{http_code} %{time_total}\n' https://www.baidu.com # 直连,< 0.2s
curl -x $P -o /dev/null -w '%{http_code}\n' https://api.anthropic.com/ # 404 即可达
curl -x $P -o /dev/null -w '%{http_code}\n' https://github.com/ # 200
出口 IP 显示的是本地运营商而不是 VPS,说明模式还在 global 且 GLOBAL 组选了 DIRECT,回到上一条踩坑。
阶段五:切换本机链路
这一阶段的目标:把所有指向旧代理端口的地方改到 7897,旧客户端退出但先不卸载。
先摸清现状再动手——我本机的旧链路是 ShadowsocksX-NG 开 SOCKS 1086 + 内置 Privoxy 转 HTTP 1087,而终端 rc 文件里根本没有代理设置,只有 ~/.claude/settings.json 的 env 块指向 1087。也就是说只有 Claude Code 及其子进程在走代理,git/gh/gradle 一直是直连。所以切换只有两处:
~/.claude/settings.json的env.http_proxy/env.https_proxy改成http://127.0.0.1:7897- Clash Verge 开「系统代理」,它会把系统 HTTP/HTTPS/SOCKS 三项全部指向 7897(浏览器走这个)
然后退出旧客户端。留两周当冷备再卸载——新线路要跑过几次晚高峰才能确认真的稳。不要把旧节点导进 Clash 做「一键切换」:那只是往个人配置里塞一段永远不会切过去的废弃加密配置。
✅ 本阶段验证
export https_proxy=http://127.0.0.1:7897
gh api /rate_limit --jq '.resources.core.remaining'
git ls-remote --heads https://github.com/<you>/<repo>.git | head -1
networksetup -getsecurewebproxy Wi-Fi # Enabled: Yes, Port: 7897
最后一项验收在晚上 8–11 点:用 Claude Code 跑一段长的流式输出,体感正常才算线路达标。白天 ping 通不说明任何问题。
支线:手机接入与日常维护
手机直接导入一条 vless:// 分享链接即可(Shadowrocket、sing-box 均可),格式:
vless://<UUID>@<VPS IP>:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=dl.google.com&fp=chrome&pbk=<PublicKey>&sid=<shortId>&type=tcp#MY-LA
Android 推荐 v2rayNG(Xray 内核,扫码即用)——注意原版 Clash for Android 已归档且不支持 VLESS/Reality,Shadowsocks 客户端只认 SS 协议,两者都用不了。导入后在「路由设置」选预设**「绕过局域网及中国大陆地址」**,它和电脑那套规则是同一个思路,多了两条:geosite:google 强制代理(防个别 Google 域名解析到国内 IP 被误判),以及阻断 UDP 443(拦掉 QUIC,逼浏览器回落 TCP 走代理,否则这部分 UDP 分流不可控)。
手机端代理可以常开:命中国内规则的流量从手机自己的网络直接出去,多出的开销只是本机进程转一手,淘宝、微信、视频体验和不开时一样;功耗一天约多 1–2%。少数银行类 App 会弹”检测到 VPN”,用时关一下即可。
日常维护表:
| 事件 | 处理 |
|---|---|
| 突然连不上 | 先 nc -z <IP> 443:通则是协议层问题,换端口或伪装目标;不通则 IP 被墙,后台换 IP 或迁机房 |
| 晚高峰变差 | 搬瓦工后台迁到 DC9 或日本机房对比,免费 |
| 例行 | 每季度 apt upgrade + 重跑一次 Xray 安装脚本升级内核 |
| 兜底 | 留一个几十块一年的商用订阅在 Clash 里,只在 VPS 挂掉时手动切 |
动手前 Checklist
- 选型三句话说得清:线路(CN2 GIA)、服务商、协议
- 下单时国家/地址真实,且提交前已关代理,页面底部 IP 是国内 IP
- 订单页已贴 SSH 公钥
- 重装系统前先 Stop 虚拟机
- 加固顺序:防火墙先放新端口 → 再改 sshd → 新终端验证后才关旧终端
- Reality
dest的证书链不超过 4 KB - 服务端先用 Xray 自己当客户端自测通过,再碰第三方客户端
- Clash 的模式、开机自启在界面上点过
- 所有设备(电脑、手机)都指向同一台 VPS,不混用其他节点
- 旧客户端保留两周冷备,晚高峰验收通过再卸载
坑速查表
| 症状 | 根因 | 修复 | 章节 |
|---|---|---|---|
| 订单进入人工审核 | 填的国家和下单 IP 对不上 | 关代理后下单,信息填真实 | 阶段二·1 |
Unable to reload a running VM | 重装需要先停机 | Main controls → Stop | 阶段二·2 |
客户端 connect error: EOF,服务端 handshake did not complete successfully | 伪装目标证书链超过 4096 字节握手缓冲 | 换证书链小的 dest(如 dl.google.com) | 阶段三 |
| Clash 出口 IP 是本地运营商 | 模式为 global 且 GLOBAL 组指向 DIRECT | 界面切「规则」并让 Verge 持久化 | 阶段四 |
| 通过 API 改的模式重启后失效 | Verge 只持久化界面操作 | 在界面上点一次 | 阶段四 |
curl https://claude.ai 返回 403 | Cloudflare 对 curl UA 的人机检查,非链路问题 | 用浏览器验证 | 阶段四 |
最后
回头看,整件事真正花时间的不是部署,而是那个 handshake did not complete successfully——一条把「认证已通过、只是缓冲装不下证书」说成「非法连接」的日志。它提醒我两件事:一是排障要先做隔离(服务端自连自己),把「谁的问题」定下来再往深挖;二是所有「教程里常见」的默认值都值得量一次,www.microsoft.com 从很多人的配置里抄过来,未必在你的版本上还成立。
至于「为什么自建」——不稳定和不可控是一对孪生问题,商用服务只解决前者的一半。一台自己控制的机器加几十行配置,换来的是接下来一年不用再想这件事。