Field entry
针对当前机场节点不稳定的一个方案-为机场订阅添加一层 VPS 中转
用一台美区 VPS,给机场订阅套一个固定出口
问题背景
目前笔者使用的是 机场订阅 这样的一个 🪜 方案,但是今年上半年服务变得非常地不稳定,在相关的技术论坛上,这似乎是一个普遍的现象,所以笔者也就没有换其他的服务商。
但是这终究是一个问题,服务不稳定,如果使用单节点,就会面临时而可用,时而不可用的窘境;但是如果让他通过 url-test 策略自由切换,又会导致出口 IP 不停变换,笔者使用 Claude 和 GPT,这两者都是有风控的,频繁切换 IP 无疑是高风险行为,所以期望能够有一套更加稳定的访问策略。

笔者有一台美区的传家宝 VPS,于是就有了本篇的一个方案改造。
核心矛盾其实很清楚:
- 机场节点多、快、但飘——出口 IP 随时切换;
- 传家宝 VPS稳、IP 固定、但线路差——直连体验很糟糕。
那能不能把两者的优点拼起来?让机场节点负责把流量安全高速地送出国,再统一从我自己的 VPS 落地。这样机场怎么抖,最终目标网站看到的都是同一个固定 IP。
目前这个方案以及平稳运行了几天,效果还不错,所以分享出来给大家做一个参考。
一、整体思路
答案是 Mihomo 的 dialer-proxy。它允许一个代理节点「不直接连出去」,而是先经过另一个代理组,再建立自己的连接。链路串起来是这样:
本地应用
│
▼
Mihomo / Clash Meta
│
├── SUB-UPSTREAM:机场订阅节点组(url-test / fallback)
│ ├── 香港节点
│ ├── 日本节点
│ └── 新加坡 / 美国节点
│
▼
US-VPS-FIXED:Shadowsocks AEAD over TCP(dialer-proxy 指向 SUB-UPSTREAM)
│
▼
目标网站最终链路为:
本地 → 机场节点 → 美国 VPS → 目标网站目标网站只能看到美国 VPS 的公网 IP。机场节点在这里退化成一条「加速管道」,负责翻墙出去;真正决定出口身份的是 VPS。Mihomo 官方也把这种配置明确列为「通过订阅节点中继自己的 VPS」的典型场景,并推荐被中继的 VPS 使用简单的 Shadowsocks AEAD 或 VMess,不要再叠 TLS、混淆这些东西——因为翻墙这一段已经由机场节点完成了。

为什么不直接用 VPS 搭建一个服务 ?
笔者在上学时也搭过自己的 🪜 服务,但是 GFW 会根据流量特征来屏蔽 vps ip,笔者在这方面没有太深的研究,所以使用的都是网上的一些方案,流量特征都属于 “记录在案” 的那些,导致用个两天 ip 就废掉了,重新开一个能直连的 ip 又很麻烦。 所以现在干脆放专业的服务商来越过 GFW ,而笔者的 VPS 只需要处理墙外的流量即可。
二、几个关键设计决策
2.1 本地必须是 Mihomo 内核
先确认客户端跑的是 Mihomo / Clash Meta,而不是早已停止维护的旧版 Clash 内核。整个方案依赖:
dialer-proxy: SUB-UPSTREAM在 Mihomo 中,dialer-proxy 可以引用具体代理或一整个代理组,用来决定当前节点通过哪个上游建立连接。旧内核没有这个能力。
2.2 服务端用 Shadowsocks-rust 官方镜像
服务端选择官方镜像:
ghcr.io/shadowsocks/ssserver-rust它提供 amd64、arm64 等多架构镜像,挂载配置文件即可运行 ssserver。建议固定版本,不要长期用 latest。笔者实际部署时用的是一台老 VPS 上早就缓存好的镜像(后文会讲到这里踩的坑)。
2.3 加密算法
方案统一用:
aes-256-gcm经典 AEAD 算法,Mihomo 和 Shadowsocks-rust 都支持。如果你的 VPS 是没有 AES 硬件加速的低性能 ARM 机器,可以换成 chacha20-ietf-poly1305。服务端和客户端的算法、密码必须完全一致。
2.4 只走 TCP
服务端设 "mode": "tcp_only",Docker 也只发布 TCP 端口、不发布 UDP。这样做的好处是攻击面更小、也避免 UDP 被滥用。
需要留意的是:TCP-only 意味着 QUIC / HTTP/3 走不了这条固定出口。绝大多数浏览器会在 QUIC 失败后自动回退到 HTTP/2,所以日常浏览无感;但纯 UDP 的应用(部分游戏、实时语音)会直接失败。要不要开 UDP,取决于你的实际用途——笔者后来在本地节点上把 udp 打开了,是因为顺手要用一些依赖 UDP 的场景,取舍自便。
三、VPS 服务端部署
下面的示例统一假设(请替换成你自己的真实值):
VPS 公网 IP:203.0.113.10 # 文档保留地址,占位用
Shadowsocks 端口:38443
加密算法:aes-256-gcm
部署目录:/opt/ss-exit3.1 创建目录并收紧权限
sudo mkdir -p /opt/ss-exit/config
cd /opt/ss-exit
sudo touch compose.yaml
sudo touch config/config.json
sudo touch config/server.acl
sudo chown -R root:root /opt/ss-exit
sudo chmod 700 /opt/ss-exit
sudo chmod 700 /opt/ss-exit/config
sudo chmod 600 /opt/ss-exit/config/config.json
sudo chmod 600 /opt/ss-exit/config/server.acl3.2 生成强随机密码
umask 077
openssl rand -base64 48会得到一串类似 XXXXXXXXXXXXXXXXXXXXXXXX...(此处省略)的随机串。
3.3 服务端配置
/opt/ss-exit/config/config.json:
{
"servers": [
{
"server": "0.0.0.0",
"server_port": 38443,
"method": "aes-256-gcm",
"password": "替换为上一步生成的随机密码",
"timeout": 300,
"acl": "/etc/shadowsocks-rust/server.acl"
}
],
"mode": "tcp_only",
"no_delay": true,
"keep_alive": 15,
"nofile": 32768
}要点:server: 0.0.0.0 监听容器地址;tcp_only 彻底关掉 UDP;acl 指向出站访问控制文件;不叠加插件 / WebSocket / TLS,保持链路简单。
3.4 出站 ACL:把 VPS 自己保护起来
这一步很关键。默认情况下,一旦密码泄露,攻击者可以把你的 Shadowsocks 当成 SSRF 跳板,去访问 VPS 内网服务、Docker 宿主机、甚至云平台元数据地址(169.254.169.254)。ACL 就是用来堵死这条路的。
/opt/ss-exit/config/server.acl:
[accept_all]
[outbound_block_list]
# IPv4 回环、私网、链路本地和保留地址
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/24
192.0.2.0/24
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/4
240.0.0.0/4
# IPv6 回环、ULA、链路本地和组播
::1
fc00::/7
fe80::/10
ff00::/8其中最要命的几条是 127.0.0.1、10/8、172.16/12、192.168/16 和 169.254.169.254——挡住它们,即使密码泄露,代理也进不了内网和元数据接口。
小提醒:手抄这份列表极易出错。笔者第一次部署时就把
169.254.0.0/16敲成了169.254.016、192.0.0.0/24写成192.0/24,CIDR 一旦写错,ACL 就形同虚设。建议直接复制,然后逐行核对。
3.5 Docker Compose
/opt/ss-exit/compose.yaml:
services:
server:
image: ghcr.io/shadowsocks/ssserver-rust:v1.24.0
container_name: ss-fixed-exit
restart: unless-stopped
user: nobody
command: ["ssserver", "--log-without-time", "-c", "/etc/shadowsocks-rust/config.json"]
ports:
- "0.0.0.0:38443:38443/tcp"
volumes:
- ./config/config.json:/etc/shadowsocks-rust/config.json:ro
- ./config/server.acl:/etc/shadowsocks-rust/server.acl:ro
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 128
mem_limit: 256m
cpus: 1.0
ulimits:
nofile:
soft: 32768
hard: 32768
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"这套配置实现了:配置文件只读挂载、容器根文件系统只读、删除全部 Linux capabilities、禁止提权、限制 PID / 内存 / CPU、日志轮转,并且不用 privileged、不用 host network、不挂 Docker Socket、不映射 UDP。
3.6 启动与验证监听
cd /opt/ss-exit
sudo docker compose config
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100
sudo ss -lntp | grep 38443预期只看到 TCP 监听,不应该出现 38443/udp。
四、防火墙与端口暴露
4.1 优先用云厂商安全组
在云平台安全组放行 TCP 38443,不要放行对应的 UDP。
如果你的机场能提供稳定的出口 IP 列表,最安全的做法是只允许这些地址连接(源 IP 白名单)。但现实中机场出口 IP 经常变,拿不到完整列表,那就只能放开:
0.0.0.0/0 → TCP 38443这时安全性完全压在高强度随机密码 + TCP-only + 出站 ACL + 速率限制 + 流量监控上。随机高端口只能减少扫描噪声,不能替代认证。
4.2 小心 Docker 绕过 UFW
一个常见误区:以为 ufw allow 38443/tcp 就万事大吉了。实际上 Docker 发布端口时,数据包在到达 UFW 的 INPUT 链之前就已经被 Docker 的 NAT 转发,UFW 规则会被绕过。正确的做法是优先用云安全组,或者写到 Docker 专用的 DOCKER-USER 链上。
4.3 可选的连接速率限制
在公网开放端口、又拿不到白名单时,可以给单个源 IP 的异常新建连接加一道闸(阈值故意放宽,避免误伤正常网页并发):
# 单源新建连接速率限制
sudo iptables -I DOCKER-USER 1 \
-p tcp --dport 38443 \
-m conntrack --ctstate NEW \
-m hashlimit \
--hashlimit-above 120/second \
--hashlimit-burst 240 \
--hashlimit-mode srcip \
--hashlimit-name ss_new_conn \
-j DROP
# 单源并发连接数限制
sudo iptables -I DOCKER-USER 2 \
-p tcp --dport 38443 \
-m connlimit \
--connlimit-above 1024 \
--connlimit-mask 32 \
-j REJECT --reject-with tcp-reset规则持久化到 /etc/iptables/rules.v4,再配一个开机 iptables-restore 的 systemd 服务即可自动恢复:
# /etc/systemd/system/iptables-restore.service
[Unit]
Description=Restore iptables rules
Before=docker.service
After=network.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target注意:机场节点是共享出口,同一个出口 IP 背后可能有一堆用户,速率阈值别设太小,先观察实际峰值再逐步收紧。
五、本地 Mihomo 配置
5.1 别直接改订阅文件
大多数图形客户端在订阅更新时会覆盖配置。要用客户端提供的 Override / Mixin / 配置覆写 / 扩展配置 功能,把下面的内容合并进去。
5.2 定义 VPS 节点
proxies:
- name: US-VPS-FIXED
type: ss
server: 203.0.113.10 # 换成你的 VPS IP
port: 38443
cipher: aes-256-gcm
password: "与服务端完全相同的随机密码"
udp: true # 需要 UDP 就开,纯稳定出口可设 false
ip-version: ipv4
dialer-proxy: SUB-UPSTREAM # 关键:先走机场组,再连 VPSdialer-proxy: SUB-UPSTREAM 这一行是整个方案的核心——它让 Mihomo 通过机场组去连 VPS,而目标网站看到的仍然是 US-VPS-FIXED 的公网 IP。
5.3 定义机场上游组
proxy-groups:
- name: SUB-UPSTREAM
type: url-test
use:
- airport # 你的机场 provider 名称
interval: 300 # 每 300 秒重测延迟
tolerance: 50 # 新节点快 50ms 以上才切换,避免抖动
lazy: true # 没被使用时不主动测速,省资源
filter: "(?i)香港|日本|新加坡|美国|韩国|台湾|HK|JP|SG|US"这里笔者用的是 url-test——因为下游 VPS 已经是固定出口,上游怎么切换都不影响最终 IP,所以让它自动挑最快的机场节点就行。
如果你更在意连接稳定性(比如上游节点掉线时不想频繁重连),也可以用 fallback:按顺序用第一个可用节点,坏了才往后切。两种都行,取舍在于「延迟最优」还是「尽量不切换」。建议把 filter 范围收在香港 / 日本 / 新加坡 / 美西,别让它自动选到欧洲、南美这种明显绕路的节点。
5.4 定义出口策略
proxy-groups:
- name: US-EXIT
type: select
proxies:
- US-VPS-FIXED需要固定出口的流量,规则里统一指向 US-EXIT 即可:
rules:
- DOMAIN-SUFFIX,openai.com,US-EXIT
- DOMAIN-SUFFIX,anthropic.com,US-EXIT
- DOMAIN-SUFFIX,github.com,US-EXIT
- GEOIP,CN,DIRECT
- MATCH,US-EXIT5.5 关于「故障降级」的取舍
有一种很自然的想法是:VPS 挂了就自动切回机场节点直出。但这恰恰是要避免的——一旦自动降级,VPS 短暂抖动时对外 IP 就变了,账号风控该触发还是会触发。
本方案更推荐 fail-closed:VPS 不可用 → 请求直接失败 → 人工决定要不要临时切到机场直出。对登录账号、API IP 白名单、自动化任务这类对出口一致性敏感的场景来说,「失败」比「悄悄换 IP」安全得多。
六、验证清单
部署完拿这几条过一遍:
# 1. 服务端只监听 TCP
sudo ss -lntp | grep 38443
# 2. 本地经代理看到的是 VPS IP
curl -x http://127.0.0.1:7890 https://api.ipify.org; echo
# 预期输出:你的美国 VPS 公网 IP然后手动在 SUB-UPSTREAM 里依次切换香港 / 日本 / 新加坡节点,每次都跑一遍上面的 curl——每次都应该返回同一个 VPS IP。切换入口时已有的 TCP 长连接可能断开重连,这是正常的,出口身份不变就对了。
最后验证 fail-closed:在 VPS 上 docker compose stop,本地访问应该失败,而不是自动露出机场 IP;再 start 恢复即可。
七、笔者踩过的坑
真机部署(一台 Ubuntu 18.04、内核 4.15 的老 VPS)时,遇到的几个问题记录在这,帮你省点时间:
| 现象 | 原因 | 处理 |
|---|---|---|
cap_drop: ALL 后容器起不来 | 默认 CMD 带 -a nobody,需要 SETUID/SETGID | 用 user: nobody + 自定义 command,去掉 -a nobody 参数 |
config.json 读取 Permission denied | 以 nobody 运行时,配置文件属组不对 | chown root:nogroup 并 chmod 640(宿主机上 nobody 对应的组名是 nogroup,不是 nobody) |
启动时 Memory limited without swap 警告 | 内核 4.15 不支持 cgroup swap limit | 忽略即可,不影响运行 |
| ACL 写了却没生效 | CIDR 手抄写错(如 169.254.016、192.0/24) | 逐行核对网段,别手抄 |
八、日常运维速查
# 状态 / 日志
cd /opt/ss-exit && docker compose ps
docker logs ss-fixed-exit --tail=50
# 重启 / 停止 / 启动
cd /opt/ss-exit && docker compose restart
cd /opt/ss-exit && docker compose down
cd /opt/ss-exit && docker compose up -d
# 确认只监听 TCP
ss -lntp | grep 38443
# 查看防火墙规则
iptables -L DOCKER-USER -n --line-numbers密码怀疑泄露时
经典单密码 Shadowsocks AEAD 不支持平滑轮换,切换期间会有短暂中断,按顺序处理:
- 安全组临时关掉 TCP 38443;
- 生成新随机密码;
- 改服务端
config.json,同步改本地US-VPS-FIXED的密码; docker compose up -d --force-recreate重启;- 重新开放端口,检查流量统计和异常连接;
- 必要时连端口一起换。
小结
绕了这么一圈,本质上就是把「机场节点」和「自有 VPS」的角色分工:机场只管把流量高速送出国,VPS 负责统一落地给出一个固定 IP。机场再怎么飘,Claude、GPT 看到的都是同一个出口,风控这一关也就稳了。
方案不复杂,真正花时间的都在细节上——ACL 的 CIDR、老内核的 cgroup、nobody 的属组、Docker 绕过 UFW 这些坑。希望这篇能帮到苦 GFW 久矣的诸君。
