Netpath
学习路径2 / 49 · 科学上网与隧道看全程 →
实验预计 40 分钟

VPS 与 anytls:自己搭一条稳定的线

决定体验的是回程线路和协议选型,不是机器配置。1 核 1G 就够,线路差三倍。

学完这节你能做到

  • 看懂 CN2 GIA / GT / AS4837 这些标签,并用 mtr 自己验证回程
  • 说清 TLS in TLS 的指纹问题,以及 anytls 用哪三招缓解它
  • 用 sing-box 与 mihomo 配出一条带真实证书的 anytls 链路

建议先学

跳过这几节会看不懂本节的部分推导

先决定要不要自建

上一节的判断树如果指到"需要一条自己的出口",第一个问题不是用什么协议,是要不要自己搭。

维度自建(VPS + 自己部署)订阅服务
成本一台小 VPS,按月固定按月,通常更便宜
可控性完全可控,出口 IP 固定出口共享,IP 可能被目标站点风控
稳定性取决于你选的线路和维护取决于服务方
隐私流量只经过自己的机器流量经过第三方
维护成本要自己管:证书续期、升级、监控无
适合长期、要固定出口、要接 CI临时、个人、不想维护

给工程师的实际建议:要在 CI 或服务器上稳定使用,自建 —— 因为你需要固定出口 IP 和可预期的可用性。个人临时用途没必要。

这一节按自建的路走一遍:选机器 → 选线路 → 配服务端 → 选协议。 协议这一步会落到 anytls,理由在后半段。

VPS:决定体验的是线路,不是配置

1 核 1G 就足够跑一个代理服务,CPU 和内存基本不是瓶颈。真正要看的是四件事:

  • 地域:香港延迟最低(大陆访问通常几十毫秒),美西便宜、机房和线路选择多,但延迟通常 在一百多到两百毫秒。做交互式操作(SSH、Web 控制台)地域的影响比带宽大得多
  • 流量与带宽:看清是"月流量配额"还是"不限量但限速"。前者用超会直接停机, 拉几个大镜像就可能触顶
  • 回程线路:同一机房不同线路的体验能差好几倍,这是最容易被价格掩盖的一项
  • 快照与重建:选支持快照的,配置搞坏了能回滚;同时看清换 IP 的政策(IP 被封时能不能换)

线路名词对照

报价页上那几个缩写是真正决定体验的东西:

标签是什么体验
CN2 GIA电信 CN2 的高优先级产品(AS4809)最好:低丢包、晚高峰不塌,也最贵
CN2 GT电信 CN2 的普通档中等:比普通骨干好,晚高峰仍会拥塞
163 / AS4134电信普通骨干最便宜,晚高峰丢包和抖动明显
AS4837 / CU4837联通骨干性价比常见选择,质量介于两者之间
Eyeball面向末端消费者网络优化的线路(eyeball network 是行业术语,指承载大量家庭宽带用户的 ISP)到家宽用户的路径更短

判断线路真假不靠商家宣传,看 traceroute 的骨干跳:途经 59.43.x.x 说明走了 CN2, 只见 202.97.x.x 则是普通骨干(这个判据来自 haoel 的科学上网笔记)。

具体推荐

下面这几家按价格从高到低、稳定性也从高到低排列 —— 这两件事在这个领域基本是正相关的:

服务商机房与线路定位
搬瓦工 bwh8.netUS · E-Commerce VPS最贵也最稳。要长期挂服务、不想操心的选它
DMIT dmit.ioUS · Premium / Eyeball次之。线路分档细,按用途挑
CubeCloud cubecloud.netHK · CN2 GIA / US · CN2 GIA性价比档。要低延迟就选香港那条
CloudCone cloudcone.comUS最便宜,稳定性最弱。适合临时验证、随时可弃

按用途对应:

  • CI 或服务器上长期用(拉依赖、跑构建)→ 取前两档。这类场景断一次就是流水线红一片, 稳定性的钱值得花
  • 个人日常看文档、查 issue → 第三档足够,香港线路的交互体验明显更好
  • 临时验证一个想法、或者只是想练一遍部署 → 最后一档,用完就删
✓下单前先自己测一遍回程

关键是回程(VPS → 大陆),不是去程。 从自己电脑 traceroute 出去测的是去程, 而中国大陆的国际链路去回程经常走完全不同的路径 —— 去程漂亮、回程拥塞是很常见的组合。

所以要在 VPS 上反向测:

# 在 VPS 上装 mtr,往大陆三网各测一个地址
mtr -r -c 20 202.96.209.133      # 电信(上海)
mtr -r -c 20 210.22.84.3         # 联通(上海)
mtr -r -c 20 211.136.112.50      # 移动(上海)

# 看骨干跳里有没有 59.43(CN2)
mtr -r -c 20 202.96.209.133 | grep -E '59\.43|202\.97'

看两件事:丢包出现在哪一跳,以及晚高峰(20:00–23:00)的延迟涨多少。 白天测都好看,晚高峰才见真章 —— 至少跨一个晚高峰再决定要不要长租。

多数商家有几天的退款窗口,先按月付测一轮再转年付,比一上来省 20% 划算。

!价格、库存与线路都会变

这个市场变动很快:机房线路会被降级、库存补货靠抢、同一款产品不同批次的实际线路可能不同。 上面的排序是长期口碑而不是当下承诺,下单前一定用上面那套 mtr 自己验一遍。

另外注意两件事:不要用同一台机器承载重要业务和这类用途(IP 被封会一起受影响), 以及账号别复用密码 —— 这类商家的安全水位参差不齐。

服务端的两项基础配置

# 1. BBR:长肥链路上收益最直接(tcp-behavior 那节讲过原因)
cat >> /etc/sysctl.conf <<'EOF'
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
EOF
sysctl -p
sysctl net.ipv4.tcp_congestion_control      # 确认生效

# 2. 域名 + 真实证书(自签会立刻暴露)
certbot certonly --standalone -d proxy.example.com
# 续期交给 cron / systemd timer,过期当天服务就断

真实证书不是"更安全一点"的选项,是必需项 —— 下一段会说清为什么。

为什么是 anytls:TLS in TLS 这个坎

现代代理协议用的都是成熟密码学,内容本身没人解得开。协议还在不停演进, 是因为要解决另一个问题:

加密流量看起来"像什么"。

即使内容不可解密,路上的设备仍然能看到这些:

特征具体内容
握手指纹TLS ClientHello 里的密码套件顺序、扩展列表、ALPN、GREASE 值
证书与 SNI目标域名(SNI 明文)、证书链、有效期
包长分布每个包多大、大小的分布形状
时序请求间隔、突发模式、连接持续时间
连接模式一个 IP 上有多少并发连接、连接建立频率
主动探测响应别人直接连你这个端口时,你返回什么

其中最核心的一个叫 TLS in TLS。代理把 HTTPS 流量装进自己的 TLS 连接时:

外层 TLS 握手(你的客户端 ←→ 你的服务器)
   └─ 建立完成后,里面开始跑:
      内层 TLS 握手(浏览器 ←→ 目标网站)
         ↑ 这个握手的包长与时序模式,在外层加密后依然有迹可循

问题在于握手是有固定形状的:ClientHello 一般偏小、ServerHello 加证书偏大、 后续几个包大小相对固定、时序也有规律。这些特征即使被外层加密包住, 包长序列和时间间隔仍然暴露在外。

于是可以判断:这条"普通 HTTPS 连接"建立后不久,里面又发生了一次 TLS 握手 —— 而正常的 HTTPS 浏览不会这样。

anytls 正是为缓解这个指纹而设计的协议,三个手段:

手段做什么减少了哪个特征
灵活的分包策略不按原始边界切,重新划分数据块包长分布
填充策略往包里塞无意义字节,改变长度包长分布
连接复用多个请求走同一条已建立的连接握手频次 —— 少产生嵌套握手

第三点值得单独说:减少嵌套握手的最好办法是不要反复建立它。 连接复用既降低了延迟(省掉握手往返),也顺带减少了可观测的握手次数 —— 一举两得,这也是它被列为核心特性的原因。

!anytls-go 是参考实现,不是生产客户端

anytls-go 的项目定位写得很明确:它是协议的参考实现, 目的是展示协议细节、交互流程与边界场景。

# 参考实现的用法,适合本地跑一遍看协议怎么工作
./anytls-server -l 0.0.0.0:8443 -p <密码>
./anytls-client -l 127.0.0.1:1080 -s "anytls://<密码>@<服务器>:<端口>"

而且它的示例服务端与客户端默认采用不安全的配置 —— 假定你不会遇到 TLS 中间人攻击。这个假定不成立时,流量可能被拦截。

日常使用要用实现了该协议的成熟软件(sing-box、mihomo), 并且自己确认 TLS 校验相关的配置是打开的。各家第三方实现与规范的贴合程度由各自团队负责, 不同版本行为可能有差异 —— 这也是"换客户端后行为变了"的常见原因。

服务端:sing-box 的 anytls 入站

{
  "inbounds": [
    {
      "type": "anytls",
      "tag": "anytls-in",
      "listen": "::",
      "listen_port": 443,
      "users": [
        { "name": "me", "password": "<用 openssl rand -base64 16 生成>" }
      ],
      "tls": {
        "enabled": true,
        "server_name": "proxy.example.com",
        "certificate_path": "/etc/letsencrypt/live/proxy.example.com/fullchain.pem",
        "key_path": "/etc/letsencrypt/live/proxy.example.com/privkey.pem"
      }
    }
  ]
}

padding_scheme 不写就用协议的默认值,它长这样:

["stop=8", "0=30-30", "1=100-400",
 "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
 "3=9-9,500-1000", "4=500-1000", "5=500-1000", "6=500-1000", "7=500-1000"]

读法:第 n 个数据包被填充到某个长度区间,stop=8 表示第 8 个包之后不再填充 —— 握手阶段是指纹最集中的地方,填充只需要盖住开头那几个包。c 是分片标记。 默认值是被调过的,没有具体理由不要自己改:改坏了反而制造出一个独一无二的指纹。

客户端:mihomo 的 anytls 出站

proxies:
  - name: my-vps
    type: anytls
    server: proxy.example.com
    port: 443
    password: "<和服务端一致>"
    client-fingerprint: chrome        # 伪装 TLS 指纹,别留默认值
    sni: proxy.example.com
    alpn: [h2, http/1.1]
    skip-cert-verify: false           # ← 生产必须 false
    udp: true
    # 连接复用的三个旋钮,默认值适用于绝大多数场景
    idle-session-check-interval: 30   # 多久检查一次空闲会话
    idle-session-timeout: 30          # 空闲超过多久就关掉
    min-idle-session: 0               # 检查时至少保留几条空闲会话

三个 session 参数就是上面"连接复用"那一招的实际控制面。把 min-idle-session 调到 1~2 能让首次请求省掉建连往返,代价是空闲时也维持着连接 —— 交互式使用值得, 长期挂机没必要。

×skip-cert-verify: true 是最常见的一处自伤

很多教程为了"先跑通"让人打开它,然后就再没关过。打开之后:证书对不对不检查、 域名匹不匹配不检查 —— 协议花力气伪装成正常 HTTPS,你却把 HTTPS 的核心保证关掉了。

如果打开它才能连通,说明证书链有问题(多半是只配了 cert.pem 而不是 fullchain.pem), 去修证书,不要关校验。

顺带一提:anytls 不能和 Reality 搭配,mihomo 文档明确说了不做支持。 要隐藏 SNI 只能走 ECH / ShadowTLS 这条路;非 Reality 不可的话,换 VLESS 系协议。

抗主动探测:三件事缺一不可

被动观察之外还有主动探测:直接连你那个端口,看它怎么回应。

正常的 HTTPS 服务:返回网页,证书匹配域名
配置粗糙的代理:  返回错误、直接断开、或返回可识别的特征响应
                  ← 这本身就是特征

三件配套的事:

  1. 真实域名 + 真实证书(Let's Encrypt 即可)—— 自签证书本身就是强特征
  2. 端口用 443 —— 非标准端口上跑 TLS 是另一个特征
  3. 端口上要有个说得过去的东西 —— 用 sing-box 的 fallback 类能力或前置一个 nginx, 把认证失败的连接转给一个真实网页,比直接断开自然得多

CDN 前置:要用就得换协议

当出口 IP 本身成为问题时(被封或被限速),可以把 CDN 挡在前面:

客户端 → CDN 边缘节点(大量正常流量共用的 IP)→ 回源到你的服务器

但这里有个必须讲清楚的限制:anytls 走不了 CDN。CDN 只代理 HTTP 语义的流量, 而 anytls 是裸 TLS 之上的私有协议,边缘节点不认。要上 CDN 就得换成 WebSocket 类传输 (VLESS + ws + tls 之类),端口也限于 80/8080/443/8443 这些标准 HTTP(S) 端口。

方案抗 TLS in TLS能过 CDN
anytls强(分包 + 填充 + 复用)否
VLESS + ws + tls弱(嵌套握手照样暴露)是

代价也很实在:多一跳、延迟增加、CDN 的连接数与流量可能产生费用, 而且回源 IP 仍可能被识别 —— CDN 只是遮住了直连关系。

i多路复用在 CDN 场景收益特别大

经 CDN 时每建一条新连接都要完整走一遍 TLS 握手 + CDN 的回源建连,开销明显。 所以走 CDN 时一定要开传输层的多路复用(mux),既降延迟又减少握手次数 —— 和 anytls 的连接复用是同一个思路,只是换了个地方实现。

另一类问题:原生 IP

有些服务不是"能不能连上"的问题,而是连上了但被目标站点风控: 数据中心 IP 会被识别并限制(流媒体、部分 AI 服务、部分电商)。

这与前面讲的流量特征无关,属于 IP 归属问题。常见处理是用 WARP 这类服务拿一个 "住宅属性"更强的出口:

# 代理模式:本地起一个 SOCKS5,只把需要的流量送过去
warp-cli registration new
warp-cli mode proxy
warp-cli connect
# 默认监听 127.0.0.1:40000

然后在客户端里只把特定域名分流到它,其余照常走自己的出口 —— 具体怎么写规则是下一节的事。

!WARP 的两个运维注意点
  • 内存泄漏:长期运行的实例存在内存缓慢增长的情况,实践中通常配一个定期重启
  • 文件描述符:并发高时要调大 LimitNOFILE,否则会出现连接失败

它更适合作为特定域名的旁路出口,而不是承载全部流量的主链路。

代价对照表

每种手段都不是免费的:

手段收益代价
灵活分包 + 填充改变包长分布有效载荷率下降(填充占带宽)
连接复用减少握手、降延迟单连接故障影响面变大(队头阻塞)
真实证书 + 伪装回落抗主动探测要维护域名与证书续期
CDN 前置遮住直连关系多一跳延迟 + 费用 + 只能用 WS 类传输
WARP 旁路解决 IP 归属多一跳 + 需要运维(重启、fd 上限)

一个务实的原则:从最简单的开始,遇到具体问题再加一层。 一上来就把所有手段叠满,结果是延迟高、配置复杂、出问题难定位 —— 而多数场景根本用不到那么多。一台 CN2 GIA 的小机器 + anytls + 真实证书, 已经覆盖了绝大部分开发者的日常需要。

检查点

Checkpoint单选

TLS in TLS 的指纹问题,本质是什么被观察到了?

Checkpoint单选

选 VPS 时,最该在下单前自己验证的是哪一项?

Checkpoint单选

已经在用 anytls,现在出口 IP 被目标站点封了,想套一层 CDN。可行吗?

这节课的落点

  • 要在 CI 或服务器上长期用才值得自建;个人临时用途,订阅服务更省事
  • VPS 上真正稀缺的是回程线路,不是 CPU 内存。看骨干跳有没有 59.43(CN2), 并且在 VPS 上反向 mtr、跨一个晚高峰再长租
  • 服务端两项基础:开 BBR、真实域名与证书(fullchain.pem,配好自动续期)
  • 协议演进不是因为加密不够强,是因为加密流量"看起来像什么"; 最核心的一个是 TLS in TLS —— 内层握手的包长与时序在外层加密后仍可识别
  • anytls 的三招:灵活分包、填充、连接复用,第三招一举两得(降延迟 + 少握手)
  • anytls-go 是参考实现且示例配置默认不校验中间人,生产用 sing-box / mihomo
  • padding_scheme 默认值是调过的,没有理由不要改 —— 改坏了等于造了个独一无二的指纹
  • skip-cert-verify 必须是 false;要打开才通说明证书链配错了,去修证书
  • anytls 不能过 CDN(裸 TLS)也不能配 Reality;要 CDN 就换 WS 类传输,代价是抗指纹变弱
  • "原生 IP"是另一类问题(IP 归属而非流量特征),用 WARP 做特定域名的旁路出口
  • 从最简单的开始,遇到问题再加一层,叠满手段只会让延迟与排障成本一起上升

延伸资料