适合自己有 VPS、喜欢折腾的人。这里讲每个组件的原理、完整的配置文件、怎么改、怎么排错和加固。 所有敏感信息都用占位符表示:
<你的VPS_IP>、<UUID>、<私钥>、<公钥>、<shortId>、<GATE_USER>、<GATE_PASS>。
iPhone/Shadowrocket
│ VLESS + XTLS-Vision + Reality(TCP,外观像访问 www.apple.com 的 TLS 1.3)
▼
VPS : <PORT> 独立 Xray 进程(systemd 服务 xray-home-<name>)
│ outbound socks → 127.0.0.1:<SOCKS_PORT>(带用户名密码)
▼
Docker:vpngw-<name>-gateway
│ microsocks(以 dante 用户运行)──fwmark 0x1──► tun0(OpenVPN 客户端)
▼
VPN Gate 节点(志愿者 / VPN Gate 自营)──► 互联网
▲
Docker:vpngw-<name>-manager(拉节点列表 → 选点 → 写 client.ovpn → 重启 gateway → 每 60 秒健康检查)
设计原则:
127.0.0.1:<SOCKS_PORT>,只有 Xray 能连到它。entrypoint.sh:先把 SOCKS 账号写进 /etc/socks.auth,因为 OpenVPN 调用 --up 脚本时会清空环境变量。然后等 /vpn/client.ovpn 出现,再循环运行 openvpn。up.sh:隧道建好后执行。dante 用户新建的连接打 0x1 标记,查 table 100 走 tun0。用 CONNMARK 保存和恢复标记,保证同一条连接的后续包也走隧道。OpenVPN 自己拨号的流量仍走 eth0。0x2 标记,查 table 101 从 eth0 回去。否则节点推下来的 0.0.0.0/1 和 128.0.0.0/1 路由会把回包吸进隧道,握手永远完成不了。-u/-P 开启认证,在容器里监听 0.0.0.0:1080,宿主机只映射到 127.0.0.1。down.sh:隧道一断就 pkill microsocks,并删掉上面那些规则。这样隧道断开时 SOCKS 会直接拒绝连接,流量不会从 VPS 自己的 IP 漏出去。vpngate.py)| 环节 | 做法 |
|---|---|
| 节点来源 | 先用官方 https://www.vpngate.net/api/iphone/,失败再用 jsDelivr 镜像 |
| 解析 | 每行 split(",", 14),因为最后一列 base64 前面的字段可能带逗号。ping 列为 - 时当作 9999,不加这一条,GB 等国家的节点会被整批丢掉 |
| 过滤 | 国家在 COUNTRIES 里、配置里有 proto tcp、能解析出 remote |
| 探测 | 4 秒 TCP 连接,连不上直接跳过 |
| 排序和选点 | 按 score 从高到低、ping 从低到高排序,从前 8 名里随机挑一个,避免大家都挤在同一个节点 |
| 改写 ovpn | 去掉 redirect-gateway;加 route <节点IP> 255.255.255.255 net_gateway;嵌入 vpn/vpn 公共账号 |
| 健康检查 | 每 CHECK_INTERVAL 秒经 gateway:1080 查一次出口 IP(换着用 4 个查 IP 的网站) |
| 换点条件 | 连续失败 3 次;出口等于 VPS 自己的 IP(泄漏);用了超过 ROTATE_MINUTES |
| 黑名单 | 换点前查 gateway 最近 5 分钟的日志,出现 AUTH_FAILED 就把当前节点拉黑 BL_SECONDS 秒 |
节点的出口 IP 常常和它的连接 IP 不一样(多出口或运营商 NAT),所以只要出口不是 VPS 自己的 IP,就算健康。
xtls-rprx-vision + Reality。dest 和 serverNames 填一个真实的 TLS 1.3 大站,有人主动探测时,看到的就是这个站的真证书。目录结构:
~/vpngw-<name>/
├── .env # GATE_USER / GATE_PASS,权限 600
├── docker-compose.yml
├── gateway/{Dockerfile,entrypoint.sh,up.sh,down.sh}
├── manager/{Dockerfile,vpngate.py}
├── ovpn/client.ovpn # manager 生成
└── state/{current.json,blacklist.json}
/etc/xray-home-<name>/{xray,config.json,client.txt}
/etc/systemd/system/xray-home-<name>.service
全部文件内容,以及一键生成它们的方法,都在同目录的 install-home-line.sh 里,可以直接读,也可以直接运行。下面只列出值得讲的部分。
services:
gateway:
build: ./gateway
container_name: vpngw-<name>-gateway
cap_add: [NET_ADMIN] # 建 tun、改路由、iptables
devices: [/dev/net/tun]
sysctls:
net.ipv4.ip_forward: "1"
ports:
- "127.0.0.1:<SOCKS_PORT>:1080" # ← 一定要写 127.0.0.1
volumes: [./ovpn:/vpn, ./state:/state]
environment:
GATE_USER: ${GATE_USER}
GATE_PASS: ${GATE_PASS}
restart: unless-stopped
manager:
build: ./manager
container_name: vpngw-<name>-manager
volumes:
- /var/run/docker.sock:/var/run/docker.sock # 用来 docker restart gateway
- ./ovpn:/ovpn
- ./state:/state
environment:
- COUNTRIES=<CC> # 可以写多个:JP,KR
- GATEWAY_NAME=vpngw-<name>-gateway
- GATE_USER=${GATE_USER}
- GATE_PASS=${GATE_PASS}
# 可选:CHECK_INTERVAL=60 ROTATE_MINUTES=60 BL_SECONDS=21600
restart: unless-stopped
实际部署中踩过的坑:manager 必须拿到
GATE_USER/GATE_PASS,否则健康检查永远失败。曾经有一条线路因此 3 小时里换了 56 次节点。
{
"log": {"loglevel": "warning"},
"inbounds": [{
"port": <PORT>, "protocol": "vless",
"settings": {"clients": [{"id": "<UUID>", "flow": "xtls-rprx-vision"}], "decryption": "none"},
"streamSettings": {"network": "tcp", "security": "reality",
"realitySettings": {"dest": "www.apple.com:443", "serverNames": ["www.apple.com"],
"privateKey": "<私钥>", "shortIds": ["<shortId>"]}}
}],
"outbounds": [
{"protocol": "socks", "settings": {"servers": [{"address": "127.0.0.1", "port": <SOCKS_PORT>,
"users": [{"user": "<GATE_USER>", "pass": "<GATE_PASS>"}]}]}},
{"tag": "block", "protocol": "blackhole"}
],
"routing": {"rules": [{"type": "field", "network": "udp", "outboundTag": "block"}]}
}
[Unit]
Description=Standalone Xray xray-home-<name>
After=network-online.target docker.service
[Service]
ExecStart=/etc/xray-home-<name>/xray run -c /etc/xray-home-<name>/config.json
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
vless://<UUID>@<你的VPS_IP>:<PORT>?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.apple.com&fp=chrome&pbk=<公钥>&sid=<shortId>&type=tcp#Home-<CC>
新版
xray x25519输出PrivateKey和Password (PublicKey),其中Password就是pbk;旧版输出Private key和Public key。
一条线路占用:一个 NAME、一个 SOCKS_PORT(只在 127.0.0.1 上)、一个对外 PORT(要在云安全列表放行),外加一套独立的 UUID、密钥和 shortId。改 install-home-line.sh 顶部的变量再运行一次就是新线路。
建议的端口规划(示例):
| 国家 | NAME | SOCKS_PORT | PORT |
|---|---|---|---|
| JP | jp | 8083 | 2083 |
| KR | kr | 8084 | 2052 |
| US | us | 8085 | 2087 |
| GB | uk | 8086 | 10086 |
看现在各国有多少节点:
curl -s https://www.vpngate.net/api/iphone/ | awk -F, 'NR>2 && NF>14 {print $7}' | sort | uniq -c | sort -rn
日本、韩国的节点通常最多。美国、英国和欧盟国家常常只有 1 到 4 个,挂了就没有备用,可以考虑 COUNTRIES=JP,KR 这种多国家兜底。
| 变量 | 默认 | 说明 |
|---|---|---|
CHECK_INTERVAL |
60 | 检查间隔(秒) |
ROTATE_MINUTES |
60 | 主动换点周期(分钟)。志愿者的会话常有时长限制。想让出口 IP 保持稳定就调大 |
BL_SECONDS |
21600 | AUTH_FAILED 节点的拉黑时长 |
FAIL_THRESHOLD |
3 | 写在代码里,连续失败几次后换点 |
改完运行 docker compose up -d 生效。
state/blacklist.json 的格式是 {"节点IP": 过期的unix时间戳}。手动拉黑或解封:
cd ~/vpngw-<name>
python3 - <<'EOF'
import json,time
p='state/blacklist.json'
try: bl=json.load(open(p))
except Exception: bl={}
bl['<节点IP>']=time.time()+86400 # 拉黑 1 天;解封就改成 bl.pop('<节点IP>',None)
json.dump(bl,open(p,'w'))
EOF
docker restart vpngw-<name>-manager
www.apple.com 可以用,换成微软的域名时握手失败。dest、serverNames 和链接里的 sni 三处要一致。curl -sI --tlsv1.3 https://<候选域名> | head -1Docker 发布端口时会在 nat/DOCKER 链里加 DNAT,并在 FORWARD 链放行。这些规则不经过宿主机的 INPUT 链,所以 ufw deny 和 iptables -A INPUT ... DROP 都拦不住。写成 "8085:1080" 就等于全网都能扫到一个 SOCKS 代理,就算带密码也会被爆破、被滥用。正确写法:
ports:
- "127.0.0.1:8085:1080"
自检,下面只应该看到 127.0.0.1:<SOCKS_PORT>:
sudo ss -lntp | grep -E ':(808[3-9])\b'
再从外部机器确认连不上:nc -vz <你的VPS_IP> 8085
N=us; P=2087; S=8085
. ~/vpngw-$N/.env
systemctl is-active xray-home-$N
sudo ss -lntp | grep -E ":($P|$S)\b"
docker ps --filter name=vpngw-$N --format '{{.Names}} {{.Status}}'
docker logs --tail 20 vpngw-$N-manager
docker logs --tail 20 vpngw-$N-gateway | grep -E 'Initialization Sequence|AUTH_FAILED|microsocks|error' || true
curl -s -m 20 --socks5-hostname 127.0.0.1:$S --proxy-user "$GATE_USER:$GATE_PASS" https://api.ipify.org; echo
cat ~/vpngw-$N/state/current.json; echo
| 现象 | 定位 | 处理 |
|---|---|---|
客户端超时,VPS 本机 ss 能看到在监听 |
云安全列表没放行 | 加入站规则 TCP nc -vz 验证 |
| 外部连接超时,本机也连不上 | iptables 里有 REJECT 规则(甲骨文镜像默认就有) | iptables -I INPUT -p tcp --dport <PORT> -j ACCEPT 后运行 netfilter-persistent save |
curl: (97) |
SOCKS 认证或握手失败。microsocks 没启动(隧道没建好),或者密码不对 | gateway 日志里有没有 Initialization Sequence Completed;核对 .env 和 Xray 出站 |
AUTH_FAILED |
节点拒绝 vpn/vpn |
manager 会自动拉黑;节点少的国家只能等 |
no usable [...] TCP nodes |
这个国家现在没有可用节点 | 换国家,或者 COUNTRIES 写多个 |
health fail 反复出现、频繁换点 |
manager 没拿到 GATE_USER/PASS;或者查 IP 的网站被节点屏蔽 | 检查 compose 里的 environment |
LEAK! |
出口等于 VPS 自己的 IP | 会自动换点;检查 up.sh 的策略路由有没有生效:docker exec vpngw-$N-gateway ip rule |
| Reality 握手失败 | pbk、sid、uuid 不匹配,SNI 不一致,或者没设 flow | xray run -test -c;对照 client.txt |
| 能连上但特别慢 | 节点带宽小 | docker restart vpngw-$N-manager 换个节点,或者把 ROTATE_MINUTES 调小 |
journalctl -u xray-home-<name> -n 50 --no-pager
# 临时把 loglevel 改成 debug 看握手过程,看完记得改回 warning
ROTATE_MINUTES,比如 360,减少主动换点。适合需要固定 IP 的网站。COUNTRIES=JP,KR,一条线路两个国家兜底。docker compose restart,日志写到 ~/check-lines.log。只在 vpngw 那几套容器里自愈,不碰其他服务。docker compose build --pull && docker compose up -d;Xray 用新版二进制替换后运行 systemctl restart。.env、config.json、client.txt 的权限都设为 600,脚本已经这样处理。bash
/etc/xray-home-<name>/xray x25519; /etc/xray-home-<name>/xray uuid; openssl rand -hex 8
# 改 config.json 后:
sudo systemctl restart xray-home-<name>sudo ss -lntp | awk '$4 !~ /^127\./',对照云安全列表,删掉不再用的规则(先确认没在用)。ssh -R)开完就关。文件内容和一个实际在跑、能正常使用的部署保持一致,manager 只做了参数化(加了 GATEWAY_NAME 变量)。云控制台的界面以实际看到的为准。