• Noesis

    VPNGate 家宽出口实战:Oracle VPS + fanout + VLESS/REALITY 全链路

    TL;DR:用 Oracle VPS 做"接线总机",fanout 拨 VPNGate 志愿者家庭宽带做真实出口,VLESS+REALITY 入站,订阅经 nginx + Cloudflare Worker 下发到 karing。零成本,真居民 ISP 出口。
    适用:想低成本获得居民 ISP 出口 IP 的个人学习;不适用:需要稳定固定 IP、高纯净度、商用的场景(看完前言再决定)。
    测试环境:2026-10,Oracle 圣何塞 ARM(Ubuntu),fanout(byJoey/fanout,main 分支,部署前请固定 commit 并通读 install.sh),xray-core,karing。
    本文所有 IP、域名、token、端口均为示例占位符,请替换成自己的;发布后建议更换示例中的 SNI 与端口。


    零、前置声明:合理使用(先看完这节)

    VPNGate 是筑波大学运营的志愿者网络,节点来自全球志愿者共享的家庭宽带。这意味着:

    - 志愿者在为你的流量背锅——他们的家庭 IP 会出现在你访问的服务的日志里;

    - 把 VPNGate 出口做成订阅分发给他人,可能违反其使用条款,也放大了对志愿者的连带风险。本教程默认仅自用;

    - 请合理使用、不要跑大流量、不要做违规的事。部署与使用后果自负。

    能接受,再往下看。


    一、架构与取舍

    局限先行

    - 节点寿命按小时计,随时会死,必须有自动重拨;

    - IP 是共享的——同一出口可能几十个人在用,商业 IP 情报库普遍把 VPNGate 段标记为 VPN/proxy。别指望它帮你过严格的账号/支付风控,“真住宅"不等于"高纯净”;

    - 节点质量参差,有的国家常年没节点(截至 2026-10:美国符合家宽过滤的极少,新加坡经常挂零)。

    架构

    数据面(你的流量真正走的路):```
    你(karing)→ VPS:1000x(VLESS+REALITY 入站)
    → netns 内 SOCKS5 → VPNGate 志愿者家庭宽带 → 目标网站
    ``订阅面(只负责下发配置,不走流量):``
    karing → Cloudflare Worker(sub.example.com)
    → VPS:10006(nginx TLS,只代理订阅 endpoint)→ fanout 面板 127.0.0.1:8899
    ```上线前必做 3 件事(别等"以后再加固")

    - 面板进程只绑 127.0.0.1,防火墙不放行面板端口;

    - 想清楚 fail-open 兜底(见第七节),别等出事;

    - 搭一个最简健康检查(第七节给逻辑),15 分钟跑一次。


    二、准备工作

    - Oracle Cloud 账号:Always Free ARM 实例,Ubuntu。注意 Oracle 有两层防火墙:VCN 的 Security List/NSG(云控制台管)+ 实例内部的 iptables(机器里管),端口必须两层同时放行,排障时一层一层看;

    - 一个域名,NS 接入 Cloudflare,例如 proxy.example.com 做回源、sub.example.com 绑 Worker;

    - Cloudflare 账号(Worker 用)。

    灰云/橙云是关键:Cloudflare 只代理 443 等少数端口(10006 不在其中),所以 proxy.example.com 必须设为 DNS only(灰云),直接解析到 VPS IP。设成橙云的话,Worker 回源和 VLESS 直连都会失败。sub.example.com 绑 Worker 自定义域名,走 CF 网络没问题。


    三、部署 fanout + 立即加固```

    先看一眼脚本再跑,main 分支换成固定 commit 更稳

    bash <(curl -fsSL https://raw.githubusercontent.com/byJoey/fanout/main/install.sh)
    systemctl enable --now fanout
    ``装完面板默认监听 0.0.0.0:8899` 且是 HTTP。先加固再用,别等第八节:

    - 进面板改掉默认密码(面板路径是安装时生成的一串随机字符,形如 /<secret-path>,后面所有地址都要带它);

    - 把面板绑到本机:settings.json 里 listen_addr 改为 127.0.0.1,systemctl restart fanout;

    • 以后进面板走 SSH 隧道,不走公网:```

    ssh -L 8899:127.0.0.1:8899 [email protected]

    本地浏览器打开 http://127.0.0.1:8899/<secret-path&gt;

    ``- iptables 只放行业务端口。**OCI 的 Ubuntu 镜像 INPUT 链尾有 REJECT**:如果链尾已有 REJECT/DROP,允许规则必须插到它前面(iptables -I),否则等于没加。加完 netfilter-persistent save`。操作前先开第二个 SSH 会话,防止把自己锁死;IPv6(ip6tables)别漏。


    四、新建出口:拨 VPNGate 节点

    面板"新建出口"→ 选国家 → fanout 从 VPNGate 列表挑节点拨号。务必打开"只用家宽节点"过滤,否则可能拨到机房节点。

    - 优先日本、韩国:节点多、延迟低、存活相对久;

    - 每个国家 1~2 条就够,多了是维护负担;

    - 美国/新加坡:截至 2026-10 前者极少通过家宽过滤(fanout 会直接拒绝"没有可用的空闲节点"),后者经常挂零,看到没有就换日韩,别硬来。

    出口体检(每条新出口都做)

    - 面板看当前出口 IP → 查 ASN,确认是居民 ISP(So-net、LG DACOM 之类);

    - 交叉验证 hosting 标记:ipinfo.io/<ip> 看 hosting 字段(whois 只能看到 ISP 名,分不出住宅还是商业专线);

    - 测速:志愿者家宽上行经常只有几 Mbps,心里有数;

    - 实测你的目标场景(流媒体解锁等),别信"理论上能过"。


    五、VLESS 入站与 REALITY

    5.1 端口规划(示例)

    fanout 建入站会随机分配高位端口(如示例里的 48650),改成安全组放行范围内的。示例规划 10001~10005(你的环境按自己的安全组定):

    端口 |
    绑定出口 |
    说明 |

    10001 |
    (不绑定) |
    VPS 直连 |

    10002 |
    日本-1 |
    住宅出口 |

    10003 |
    韩国-1 |
    住宅出口 |

    10004 |
    日本-2 |
    住宅出口 |

    10005 |
    韩国-2 |
    住宅出口 |

    改端口走面板 API。密码别写进命令行(会进 shell history 和 ps),用环境变量或交互输入:```
    read -s PANEL_PW
    curl -s -b cookies.txt -c cookies.txt -d "password=$PANEL_PW" \
    http://127.0.0.1:8899/<secret-path>/login > /dev/null
    curl -s -b cookies.txt \
    "http://127.0.0.1:8899/<secret-path>/api/panel/inbound/update?id=<入站ID>&port=10002&quot;
    chmod 600 cookies.txt # 用完删掉
    ``改完确认 xray 路由绑定还在(in-10002-tcp → fanout-vpn<id>`)。

    5.2 REALITY 参数(概念先分清)

    - dest:服务端伪装/回落目标站;

    - serverNames(服务端)/serverName(客户端 SNI):握手时发送的域名;

    - fingerprint:客户端 TLS 指纹(如 chrome),和 SNI 是两回事;

    - privateKey/publicKey:REALITY 密钥对(面板建入站时自动生成);

    - shortId:客户端与服务端匹配用的短 ID,别手改。

    REALITY 不用你为每个入站申请证书,借大厂域名的 TLS 握手做伪装。flow 留空即可(xtls-rprx-vision 按需开,它是客户端属性,改动不涉及换 UUID)。

    5.3 SNI 选择与分散(建议级别)

    同一 IP 上的 5 个端口本来就能被聚类,"分散 SNI"只是降低关联特征的经验做法,不是协议要求。建议:

    - 每个入站用不同的大厂域名做 SNI(示例:tesla / apple / amazon / bing / cloudflare——示例而已,别照抄);

    - 选站标准:支持 TLS1.3、看着像正常网站、最好与 VPS 地域别差太远;

    - fanout 自带的 dest 检查通过 ≠ 客户端能连,必须做端到端验证。

    实测坑(截至 2026-10):www.microsoft.com 做 SNI,服务端检查能过,但从圣何塞发起 REALITY 握手连续失败,换 www.bing.com 一次通过。SNI 定下来之前先验证:```
    openssl s_client -connect www.bing.com:443 -tls1_3 -alpn h2 </dev/null | head -5
    ```5.4 端到端验证(两步)

    - VPS 上起 xray 客户端,走入站的 vless 链接做 SOCKS 出口,curl -x socks5h://127.0.0.1:11801 https://api.ipify.org,返回 IP 与面板显示的出口一致;

    - 本地用 karing 实连一次——VPS 上通不代表你的网络路径通。


    六、订阅链:证书 → nginx → Worker

    6.1 先签证书

    Let’s Encrypt 的 HTTP-01 验证走的是 80 端口,和 443 无关。所以:443 没放行不影响申请,只要 80 通;80 也不通才需要 DNS-01。```

    80 已放行时最简单(示例拓扑里 80 是开的,顺便做 301 跳转)

    certbot certonly --nginx -d proxy.example.com

    续期由 systemd timer 自动处理;定期验证:

    certbot renew --dry-run
    ``注意顺序:**先有证书,再写 nginx 的 ssl 配置**(否则 nginx -t` 直接失败)。签完把子域名记下来——证书会进 CT 公开日志,子域名是藏不住的。

    6.2 nginx:只暴露订阅 endpoint```
    server {
    listen 10006 ssl;
    server_name proxy.example.com;

    ssl_certificate /etc/letsencrypt/live/proxy.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/proxy.example.com/privkey.pem;

    access_log off; # 订阅 URL 带 token,会被明文记进日志——关掉是降低泄露面,不是根治
    server_tokens off;

    # CF 回源 IP 白名单:只是挡掉直连扫描的噪音,不是认证!
    # 任何人的 CF 站点/Worker 出口都在这些段里。完整列表:
    # curl -s https://www.cloudflare.com/ips-v4 https://www.cloudflare.com/ips-v6
    # 拼成 allow 行(v4+v6 都要),最后 deny all
    allow 173.245.48.0/20;
    # ...(全部填上,别学示例只写两段就上线,会把正常回源 403 掉)
    deny all;

    location = /<secret-path>/sub {
    proxy_pass http://127.0.0.1:8899;
    }
    location / {
    return 404;
    }
    }
    ``- location = 精确匹配是重点:写成 location /` 全代理等于把面板登录页挂公网;

    - 端口用 10006 是因为示例里 OCI 安全组没放 443;你的厂商放行了 443 就直接用 443;

    - 想要真认证:上 Authenticated Origin Pulls(mTLS),或 Worker 加自定义头 + nginx 校验。

    6.3 Cloudflare Worker:订阅中转```
    // 建议用 Module Worker(export default)+ wrangler;
    // 我们当时用 API 单文件上传时 module 格式被拒(10021),
    // 才用的 service-worker(addEventListener)格式——那是上传方式问题,不是"必须"
    const UPSTREAM = 'https://proxy.example.com:10006&#039;; // 别硬编码 IP:Worker subrequest 不支持直接对 IP 发起(我们实测报 1003),一律用域名

    export default {
    async fetch(req, env) {
    const url = new URL(req.url);
    // 只放行订阅路径,别做成开放转发器
    if (url.pathname !== '/<secret-path>/sub') {
    return new Response('Not Found', { status: 404 });
    }
    let resp;
    try {
    resp = await fetch(UPSTREAM + url.pathname + url.search,
    { headers: { 'User-Agent': 'Mozilla/5.0' } });
    if (!resp.ok) throw new Error('upstream ' + resp.status);
    } catch (e) {
    return new Response('Bad Gateway', { status: 502 });
    }
    let body = await resp.text();
    try {
    // 最小实现:假设订阅是标准 base64;base64url/分行等情况需另行处理
    let decoded = atob(body.trim());
    decoded = decoded.split('198.51.100.10').join('proxy.example.com');
    body = btoa(decoded);
    } catch (e) {
    return new Response('Bad Gateway', { status: 502 });
    }
    return new Response(body, {
    status: 200,
    headers: {
    'Content-Type': 'text/plain; charset=utf-8',
    'Cache-Control': 'no-store', // 带 token 的订阅别被缓存
    },
    });
    }
    };
    ``说明:这是最小实现——透传时丢了上游的 subscription-userinfo` 等头(karing 会用),生产用建议补上;Worker 只是转发层,不做 token 鉴权,别误会。

    最终订阅地址:https://sub.example.com/<secret-path>/sub?token=<SUB_TOKEN>。

    6.4 验证订阅```
    curl -s -A "Mozilla/5.0" \
    "https://sub.example.com/<secret-path>/sub?token=<SUB_TOKEN>&quot; \
    | base64 -d | grep -o "sni=[^&#]*" | sort | uniq -c

    期望 5 个不同 sni 各 1 次;另检查 UUID 不重复

    ```curl 带 -A:CF 会拦默认 UA(Python urllib 不带 UA 拿过 403,虚惊一场)。


    七、fail-open:最隐蔽的风险

    实测:隧道死掉后,xray 里对应入站的路由规则会消失,流量 fallback 到默认出站(direct = VPS 本机出口)。客户端毫无感知,等于静默切成了机房 IP。

    我们的做法(15 分钟跑一次):

    - 对每条入站做 REALITY 握手,验证出口 IP 不是 VPS 本机 IP(最好连 ASN 一起对,连续多次确认);

    - 连续 2 次失败 → 调面板 API 自动禁用该入站(连不上,好过静默走直连);

    - 之后每轮恢复探测:重开 → 握手验证 → 通了保持开启并告警,没通重新禁用;

    - 手动禁用的永不自动碰;同一故障只告警一次。

    更彻底的结构性兜底:在 xray 路由末尾给住宅入站加一条 → blackhole 的 catch-all(10001 直连显式走 direct),路由丢了就断流不断泄。但要先确认 fanout 重写配置时不会覆盖这条——没验证过,列出来供参考。


    八、日常维护(每月看一眼)

    - 健康检查有没有持续 ALERT 的隧道;

    - certbot renew --dry-run 确认续期链路;

    - CF 官方 IP 段变更 → 同步 nginx 白名单;

    - 备份:fanout 配置目录(面板密码、sub_token、入站配置)+ nginx 站点配置,打包加密异地存,口令单独保管;备份完最好做一次恢复演练;

    - 准备一个备用订阅域名,万一主域名被封可迁移。


    九、避坑速查表

    症状 |
    原因 |
    解法 |

    新端口本地通、公网不通 |
    iptables 链尾 REJECT 吃了规则 / 安全组没放 |
    iptables -I 插前面 + netfilter-persistent save;检查云控制台安全组 |

    面板绿勾但客户端连不上 |
    fanout 的 dest 检查只是服务端视角 |
    xray 客户端端到端握手验证(5.4) |

    karing 订阅 502 |
    裸 IP+非标端口被拦截;或刷新请求走了代理自身 |
    走 Worker 订阅链;订阅域名加直连规则;偶发时可临时关 VPN 排查 |

    某天突然变成机房 IP |
    隧道死了,fail-open |
    第七节健康检查 + 自动禁用 |

    能连但网页转圈 |
    链路 UDP/QUIC 不通 |
    karing 里禁用 QUIC/HTTP3 |

    大页面/下载卡死 |
    嵌套隧道 MTU 问题 |
    调小 mssfix / tun-mtu |

    换了 SNI 还是握手失败 |
    目标站回包策略问题 |
    换 SNI(microsoft→bing 实测案例),用 s_client 先验证站点 |


    十、备选与进阶

    - 嫌 nginx+证书麻烦:可以试试 cloudflared tunnel 或"VPS 定时把订阅推到 Workers KV",整套回源都不用开了;

    - 要更强伪装再看 Hysteria2 等,但先把这套跑稳。


    初稿 v2。实测 4 条住宅隧道(日本×2、韩国×2)+ 1 条直连。教程里的"血泪"都是真踩出来的,欢迎指正。

    主楼
  • Trent

    github有类似的项目,AimiliVPN

    #1

发表回复

登录后回复