Files
Private-Proxy/server_config

RRPP 服务端部署规范

一套统一的跨境代理节点部署流程,适用于 Debian/Ubuntu 系 Linux。 每台节点运行相同的服务栈:BBR 拥塞控制 + nginx CONNECT 隧道 + sing-box(VLESS+REALITY / Shadowsocks) + mtg(MTProto)。

本文档为通用规范,所有 IP / 域名 / 端口 / 密钥均以占位符表示,部署时替换为实际值。 凡标注 <...> 的均为占位符,不要原样照抄。


目录


0. 架构总览

客户端 ──► nginx(443/<nginx端口>) ──┬─► CONNECT 隧道(任意端口, 全端口放行)
        │                          └─► 伪装页面(直接访问/IP访问)
        │
        ├─► sing-box(<vless端口>) ──► VLESS+REALITY(vision)
        ├─► sing-box(<ss端口>)    ──► Shadowsocks(2022-blake3-aes-256-gcm)
        └─► mtg(<mtproto端口>)    ──► MTProto(Fake TLS, Telegram 内置代理)

四层代理各司其职:

层 组件 协议 适用场景
HTTP 隧道 nginx CONNECT + Basic Auth 浏览器/curl/系统代理,全端口 WebSocket 透传
通用代理 sing-box VLESS+REALITY Shadowrocket/Clash/v2rayN 客户端
兼容旧客户端 sing-box Shadowsocks Shadowrocket 2.x 等不支持 REALITY 的客户端

核心密钥体系(2026-09 起全部每节点独立):

  • VLESS+REALITY:每节点独立 UUID、REALITY 密钥对、short_id;SNI 仍全节点统一(用 <伪装SNI域名>,选一个高流量、TLS 指纹稳定的知名站点域名)
  • Shadowsocks:每节点独立 PSK,方法 2022-blake3-aes-256-gcm(SIP022,抗重放探测)
  • nginx CONNECT:每节点独立 Basic Auth 密码(客户端按节点携带凭据,无兼容性问题)
  • MTProto:每台独立 secret(Fake TLS 类型,fronting 到 <伪装SNI域名>)

独立化的目的:单节点密钥泄漏/被污染时不波及其余节点,且换钥粒度更小。 代价是订阅/nodes.json 必须逐节点维护,改任一项后必须做端到端验证(见 6.3、第 10 节)。


1. 端口与服务规划

端口全部落在同一区段便于防火墙管理(示例用 1200–1220,可按需调整):

端口 服务 说明
<nginx端口> nginx HTTPS 站点 + CONNECT 隧道(示例 1205)
<vless端口> sing-box VLESS+REALITY(示例 1206)
<ss端口> sing-box Shadowsocks(示例 1207)
<mtproto端口> mtg MTProto(示例 1210)

节点 A 用 80xx 区段、节点 B 用 120xx 区段皆可,只要同节点内不冲突、且运营商/防火墙放行。 防火墙放行:iptables -A INPUT -p tcp -m multiport --dports <nginx端口>,<vless端口>,<ss端口>,<mtproto端口> -j ACCEPT

域名规划: 每个节点一个独立域名 <节点域名>(如 node1.example.com),A 记录指向节点公网 IP。acme.sh 用 DNS-01 签证书,无需 80 端口可达。


2. 系统基础优化(BBR 跨境丢包优化)

BBR(Bottleneck Bandwidth and RTT)+ fq 队列,显著改善跨境 TCP 丢包/高延迟下的吞吐。

2.1 启用 BBR

写入 /etc/sysctl.d/99-bbr.conf:

net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

应用并验证:

modprobe tcp_bbr 2>/dev/null || true   # 部分内核需手动加载模块
sysctl --system
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
# 期望输出: bbr / fq

内核要求:BBR 需 Linux 4.9+。default_qdisc=fq 在部分新内核(6.12+ cloud 内核)可能无此参数,仅保留 tcp_congestion_control=bbr 即可,不影响 BBR 生效。

2.2 文件描述符

nginx worker 高并发需要提高 limit,在 /etc/security/limits.conf 追加:

* soft nofile 65535
* hard nofile 65535

2.3 时区与 NTP

代理证书校验、日志排障都依赖准确时间:

timedatectl set-timezone Asia/Shanghai
apt install -y chrony && systemctl enable --now chrony

2.4 PMTUD 黑洞缓解(tcp_mtu_probing)

症状:部分客户端网络上出现"TCP 能连上、但 TLS 握手稳定超时" (如 _ssl.c:1011 handshake operation timed out),而服务器从其他网络探测一切正常。 原因是客户端↔服务器路径上某跳 MTU 变小且 ICMP Fragmentation Needed 被过滤 (PMTUD 黑洞),服务器发出的满尺寸大包永远到不了客户端。

开启 RFC 4821 PLPMTUD,让服务器在探测失败后自动降低 MSS 恢复通信。 写入 /etc/sysctl.d/99-mtu-probing.conf:

net.ipv4.tcp_mtu_probing = 1
sysctl --system
sysctl net.ipv4.tcp_mtu_probing   # 期望输出: 1

只影响命中黑洞的连接,正常连接无感知,可安全全节点开启。

2.5 fail2ban(sshd 爆破防护)

节点 SSH 暴露在公网,爆破尝试是常态(实测单节点累计 3 万+ 次)。装 fail2ban 护 sshd:

apt install -y fail2ban
cat > /etc/fail2ban/jail.d/sshd.local << 'EOF'
[sshd]
enabled = true
backend = systemd
EOF
systemctl enable --now fail2ban
fail2ban-client status sshd   # 确认 jail 在跑
  • backend = systemd:无 /var/log/auth.log 的最小化系统(无 rsyslog)必须加这行, 否则 fail2ban 启动报 "Have not found any log file for sshd jail"。
  • LXC 容器也能正常封禁(实测 ban/unban 链路可用)。
  • 只加 fail2ban、不动 sshd 认证方式(root 登录等现状保留)。
  • 如需同时护 nginx CONNECT 的 401 爆破,可再加 nginx jail(凭据够强时可不做)。

2.6 系统级网络优化(KVM / LXC 差异)与 qdisc 持久化

模板:sysctl/99-zz-rrpp-network.kvm.conf(完整 VM,含 Azure 等)、 sysctl/99-zz-rrpp-network.lxc.conf(容器) 落地位置统一为 /etc/sysctl.d/99-zz-rrpp-network.conf; qdisc 伴侣:systemd/rrpp-qdisc.service + scripts/rrpp-qdisc.sh。

# KVM / 完整 VM
install -m 644 sysctl/99-zz-rrpp-network.kvm.conf /etc/sysctl.d/99-zz-rrpp-network.conf
# LXC / OpenVZ 容器
install -m 644 sysctl/99-zz-rrpp-network.lxc.conf /etc/sysctl.d/99-zz-rrpp-network.conf
# qdisc 持久化(仅完整 VM 有意义;容器内网卡是 noqueue,脚本会自动跳过)
install -m 755 scripts/rrpp-qdisc.sh /usr/local/sbin/rrpp-qdisc.sh
install -m 644 systemd/rrpp-qdisc.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now rrpp-qdisc
sysctl --system

为什么文件名是 99-zz-:sysctl 按文件名字典序加载、后者覆盖前者。99-zz- 保证本文件排在最后, 避免被其它 99-*.conf 覆盖。

⚠ 最容易踩的坑:/etc/sysctl.conf。sysctl --system 会在所有 sysctl.d 之后再加载 /etc/sysctl.conf。 2026-09 实测:某节点(aq)存在 2022 年的一键优化版 /etc/sysctl.conf,把 tcp_mtu_probing 覆盖回 0, 导致 sysctl.d 里的设置完全不生效(现象:sysctl -n 与文件内容不一致)。 处理:该文件里凡是与本规范重叠的键,逐行注释掉(保留其非冲突的调优,如 conntrack、rps、swappiness), 或直接把本规范内容追加到该文件末尾。排查方法:

sysctl -p /etc/sysctl.d/99-zz-rrpp-network.conf   # 显式加载后应看到期望值
sysctl --system 2>/dev/null | grep -n tcp_mtu_probing | tail -5   # 看谁在最后覆盖

KVM / 完整 VM 可写(本规范设置项)

项 值 作用
tcp_congestion_control / core.default_qdisc bbr / fq BBR 需 fq 搭配,否则 bufferbloat
tcp_mtu_probing 1 PMTUD 黑洞缓解(见 2.4)
tcp_fastopen 3 客户端+服务端 TFO,省一次 RTT
tcp_slow_start_after_idle 0 空闲后不回退慢启动(长连接代理有益)
tcp_tw_reuse / tcp_fin_timeout 2 / 15 TIME-WAIT 复用与回收
tcp_keepalive_time/intvl/probes 300 / 30 / 5 及时清理死连接(默认 7200 过长)
ip_local_port_range 10240 65000 出站端口范围,防高并发端口耗尽
core.somaxconn / tcp_max_syn_backlog 32768 accept / 半连接队列
core.netdev_max_backlog 32768 网卡收包队列(默认 1000 易突发丢包)
core.rmem_max / wmem_max 33554432 socket 缓冲上限(默认 208KB 偏小)
tcp_rmem / tcp_wmem 4K/128K/32M、4K/16K/32M 自动调优范围

LXC / OpenVZ 容器不可用(由宿主机控制,实测 2026-09)

项 现象
core.rmem_max / core.wmem_max 不存在或 -r--r--r-- 只读
core.default_qdisc 不存在(sysctl 报 cannot stat)
core.netdev_max_backlog 缺失
默认路由网卡根 qdisc noqueue,无法设置 fq
/usr/lib/sysctl.d 的 kernel.pid_max、fs.protected_* permission denied——容器内属正常现象,与本文档无关

结论:容器节点只能调 netns 级项(拥塞算法、MTU 探测、TFO、TW 复用、keepalive、 somaxconn、syn backlog、本地端口范围);socket 缓冲上限与 qdisc 需服务商在宿主机设置。 因此容器节点想进一步提升,应优先在应用层/传输层做(例如走 UoT 而不是直连 UDP)。

qdisc 为什么必须单独用 systemd 单元:net.core.default_qdisc=fq 只对新建网卡生效; 已存在的网卡即使重启也可能仍是 pfifo_fast / fq_codel(实测:aq 为 pfifo_fast、us 为 fq_codel)。 rrpp-qdisc.service 在开机后显式执行 tc qdisc replace dev <默认路由网卡> root fq 补齐; 脚本对容器(noqueue)会自动跳过。

UDP 策略(2026-09-20 定稿):UDP 传输(Hysteria2 / TUIC)曾于当日全节点部署并端到端验证, 但基于「抗封优先」的评估,当晚已全部撤除(详情与留档见 §6.4)。现行口径:

  • 节点不部署 UDP 传输服务;UDP 需求统一走 UoT v2(§5.5)——数据报封装进 TCP CONNECT 隧道, 经 nginx magic 地址转本机 127.0.0.1:15201 relay 解封装;
  • 抗封主路径 = REALITY + Vision(TCP),不做 fallback 组(订阅仍为 select);
  • 节点上的 UDP 系统调优项保留(无害,且对 UoT / SS-UDP 有轻微收益)。

撤除理由(实测 + 推演):非标准 UDP 端口被封对 GFW 几乎零代价(不像封 TCP 443 会误伤大量正常 HTTPS); UDP 在 CN 长期被 QoS/限速;且一旦 IP 因 UDP 特征被判为代理,会连带打死同 IP 上的 TCP 服务(REALITY/SS/nginx)。 加密性本身没有问题(QUIC=TLS1.3,实测证书校验强制、错凭据被拒)——风险在可封禁面,不在密码学。

节点侧 UDP 相关系统调优(保留):

项 值 / 说明
net.ipv4.udp_rmem_min / udp_wmem_min 8192(容器内可写,实测 LXC 同样生效)
net.core.rmem_max / wmem_max 33554432(KVM 可写;LXC 缺失/只读)
rx-udp-gro-forwarding / rx-gro-list(ethtool) 由 rrpp-qdisc.sh 开机开启;实测支持情况:us/jp ✅ 两项;aq(内核 5.10)仅 rx-gro-list;LXC 容器无 netdev 权限,无法设置
QUIC/UDP 吞吐 受宿主机 socket 缓冲上限约束——LXC 节点上这是硬约束,需服务商在宿主调 rmem_max

明确不采用的手段:锐速/LotServer(闭源、绑定旧内核)、net-speeder(重复包、已废弃)、 tcp_tw_recycle(4.12 起内核已移除,开启会破坏 NAT 后客户端)、tcp_notsent_lowat 激进值 (可能压制高 BDP 吞吐)、busy_poll/busy_read(1 vCPU 机器上代价大于收益)、各类"一键优化脚本" (多为本文档子集 + 心理安慰,且常与 sysctl.d 冲突)。

落地状态(2026-09-20)

节点 虚拟化 落地情况
hk2 LXC(Debian 12) LXC 模板;bbr + mtu_probing=1 + fastopen=3 + 队列/端口项;缓冲与 qdisc 受宿主限制
ru LXC(Debian 13 cloud 内核) 同上(rmem_max/wmem_max 只读)
us KVM(Debian 12) KVM 模板 + fq(原 fq_codel)
aq KVM(Debian 11 cloud) KVM 模板 + fq(原 pfifo_fast);并注释其 /etc/sysctl.conf 中 17 行冲突键(原 tcp_mtu_probing=0)
jp Azure VM(Ubuntu 6.17) KVM 模板(经 sudo 落地;原有 99-tune.conf 保留)

3. 伪装站点页面

直接访问节点/IP 时返回一个普通网页(而非代理错误),降低被探测的特征。

放置一个静态 index.html 到 /var/www/html/index.html:

  • 任意抓取的合法 HTML 页面(博客/门户首页快照均可)
  • 所有节点用同一份(md5 一致),避免逐节点特征差异
mkdir -p /var/www/html
# index.html 为本仓库 fake-site/index.html 的拷贝
cp fake-site/index.html /var/www/html/index.html

4. HTTPS 证书(acme.sh DNS-01)

nginx 站点与 CONNECT 隧道共用一张证书。用 acme.sh + DNS-01(以阿里云 DNS 为例,其他 DNS 商同理):

curl https://get.acme.sh | sh -s email=<你的邮箱>
export Ali_Key="<阿里云AccessKey>"
export Ali_Secret="<阿里云Secret>"

~/.acme.sh/acme.sh --issue --dns dns_ali -d <节点域名> --keylength ec-256

# 安装到 nginx 读取路径
mkdir -p /etc/nginx/ssl/<节点域名>
~/.acme.sh/acme.sh --install-cert -d <节点域名> --ecc \
  --fullchain-file /etc/nginx/ssl/<节点域名>/fullchain.pem \
  --key-file       /etc/nginx/ssl/<节点域名>/privkey.pem

acme.sh 自带 cron 自动续期。证书路径固定为 /etc/nginx/ssl/<节点域名>/{fullchain,privkey}.pem。


5. nginx 源码编译 + CONNECT 隧道

标准 nginx 不自带 CONNECT 方法支持,需打 ngx_http_proxy_connect_module 补丁后源码编译。

5.1 编译

apt install -y build-essential libssl-dev libpcre3-dev zlib1g-dev curl wget

NGINX_VER=1.26.3
cd /usr/local/src
curl -LO http://nginx.org/download/nginx-$NGINX_VER.tar.gz
tar xzf nginx-$NGINX_VER.tar.gz

git clone https://github.com/chobits/ngx_http_proxy_connect_module.git
cd nginx-$NGINX_VER
patch -p1 < ../ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch

./configure \
  --prefix=/usr/local/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --pid-path=/var/run/nginx.pid \
  --error-log-path=/var/log/nginx/error.log \
  --http-log-path=/var/log/nginx/access.log \
  --with-http_ssl_module \
  --with-http_v2_module \
  --with-http_realip_module \
  --with-http_stub_status_module \
  --with-http_gzip_static_module \
  --add-module=../ngx_http_proxy_connect_module
make -j$(nproc) && make install

若发行版 nginx 已自带 proxy_connect 补丁(极少见),可用 apt install nginx 跳过编译,但源码编译是通用做法。

5.2 Basic Auth 凭据

CONNECT 代理需要 Basic Auth(用户名/密码),每节点独立密码(客户端配置按节点携带凭据,无兼容性问题)。生成 htpasswd:

apt install -y apache2-utils
htpasswd -bc /etc/nginx/.htpasswd <代理用户名> '<本节点独立密码>'
chmod 644 /etc/nginx/.htpasswd   # worker 以 nobody 运行,必须可读

无 apache2-utils 的机器可用 openssl 生成(sha512-crypt,nginx 同样支持): echo "<用户名>:$(openssl passwd -6 '<密码>')" > /etc/nginx/.htpasswd htpasswd 是每次请求实时读取的,改密码无需 reload nginx。

5.3 nginx.conf

完整配置见 nginx/nginx.conf。关键设计:

  • 两个 server 块共用 <nginx端口>:
    • 主块 server_name <节点域名>:证书站点 + location / 返回伪装页;非本域名一律 rewrite 到 /__proxy 转发
    • fallback 块 default_server:其他 SNI 也接受 CONNECT 代理;本机/私网/公网 IP 直连返回伪装页,其余走 /__proxy
  • proxy_connect_allow all;:全端口 CONNECT 放行(codex/WebSocket 走任意端口均通)。若需限制,改回 proxy_connect_allow 443 563;
  • auth_basic:仅 CONNECT/正向代理需要认证,location / 伪装页 auth_basic off
  • 自代理回环保护:/__proxy 转发时带 X-Proxy-Loop-Guard 头,二次进入返回 508 防死循环
  • /__local:本机 IP 直连时剥前缀返回伪装页
  • map $host $is_local:私网/回环段判定;公网 IP 由 cron 脚本动态写入 local_ip_rules.conf

5.4 自代理回环保护说明

正向代理转发目标若指向本机自己(如域名公网 IP 变更、或代理目标就是本节点),会无限循环。两层防护:

  1. map 判定私网/回环段 + cron 维护的本机公网 IP 规则 → 直连伪装页
  2. /__proxy 的 Loop-Guard 头兜底 → 二次进入返回 508

5.5 路线 B 裸 UoT relay

TUN 客户端的 UDP 会先通过认证 CONNECT 请求 sp.v2.udp-over-tcp.arpa:443。当前 hk2 使用的 ngx_http_proxy_connect_module 没有注册 $connect_host,因此在 fallback server 实际接收的 CONNECT 请求上用 $request 正则匹配,再把连接转到 127.0.0.1:15201;nginx 只负责 TLS、Basic Auth 和字节流转发,不解析 UoT。relay 使用仓库中的 [uot_relay.py](uot_relay.py),只监听回环地址,不需要 sing-box:

install -m 755 uot_relay.py /opt/rrpp-uot-relay/uot_relay.py
install -m 644 systemd/uot-relay.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now uot-relay
ss -lntp | grep 127.0.0.1:15201
nginx -t && systemctl reload nginx

客户端的 CONNECT 端口必须大于 0,当前统一使用 443;15201 是服务器回环 relay 的内部端口,不写入客户端 CONNECT 目标。nginx fallback 块使用 proxy_connect_read_timeout 10m 和 proxy_connect_send_timeout 10m,不要再同时 配置会产生 duplicate 的 proxy_connect_data_timeout。


5.5 UoT(UDP over TCP)支持

HTTP CONNECT 只隧道 TCP。要让 UDP 应用(DNS/游戏/QUIC 等)走代理,需 UoT v2 (SagerNet 专有协议,见 sing-box 文档 configuration/shared/udp-over-tcp): 客户端把 UDP 数据报封装进 TCP 流,经 CONNECT 隧道发到 magic 地址 sp.v2.udp-over-tcp.arpa,由本机 UoT relay 解封装后转发真实 UDP 目标。

客户端 TUN/UDP ──UoT v2──▶ TLS HTTP CONNECT ──▶ nginx proxy_connect
    ──▶ 127.0.0.1:15201(uot-relay)──▶ 真实 UDP 目标

relay 部署(Python 标准库实现,见 uot-relay/uot_relay.py):

install -d /opt/rrpp-uot-relay
install -m 755 uot-relay/uot_relay.py  /opt/rrpp-uot-relay/uot_relay.py
install -m 644 uot-relay/uot-relay.service /etc/systemd/system/uot-relay.service
systemctl daemon-reload && systemctl enable --now uot-relay

relay 只监听 127.0.0.1:15201,空闲 9 分钟断开(须小于 nginx 隧道超时 10m)。 支持 UoT v2 两种模式:isConnect=1 固定目标、isConnect=0 每帧自带目标。

nginx 侧(模板已含,nginx/nginx.conf):map $request 识别 magic 地址 → 127.0.0.1:15201,其余 CONNECT 走 $connect_addr 默认解析; proxy_connect_address $rrpp_uot_connect_address; 放在 default_server 块。

四个实测坑(2026-09 全节点部署验证):

  1. 该模块版本未注册 $connect_host 变量(README 有、源码无)→ 必须用 $request 正则匹配(任何阶段可用);
  2. proxy_connect_data_timeout 与 proxy_connect_read_timeout 写同一配置槽, 同时写报 duplicate → 长连接超时用 read/send timeout 设 10m;
  3. 所有 CONNECT 请求都落在 default_server(fallback)块——nginx 按请求 Host 头选虚拟主机,CONNECT 的 Host 是目标域名,永远匹配不到 server_name;
  4. 模块拒绝 CONNECT 端口 0(port < 1 返回 400)→ 客户端连 magic 地址时 端口必须非 0(用 443 即可,relay 不读 CONNECT 端口,目标取自 UoT 帧头)。

健壮性要求(2026-09 实测修复,别踩):

  • relay 必须用未连接的 UDP socket(sendto/recvfrom)。用连接式 socket 时, 目标侧 ICMP(port unreachable 等)会变成 ConnectionRefusedError 在 send/recv 上抛出, 若被兜底 except 接住并 stop.set(),一条数据报出错就杀掉整条 UoT 隧道 (客户端表现为 "UoT TCP 流已关闭"、多条隧道同秒集体死亡)。
  • 数据报级错误(BlockingIOError/EAGAIN、ENETUNREACH、EHOSTUNREACH、EMSGSIZE) 只丢当前包,EAGAIN 先等可写再重试;只有 TCP EOF、协议错误、空闲超时才关隧道。
  • 按地址族惰性建 socket,non-connect 模式可混用 IPv4/IPv6 目标;IPv6 recvfrom 返回 4 元组需归一化。
  • connect 模式要预建 socket(否则主循环首轮无可 select 对象,会立刻关连接)。

节点出口差异(实测):RU 节点到境外 DNS(8.8.8.8:53)UDP 成功率仅约 4/10 (俄 ISP 限制),表现为 UoT DNS 偶发超时——属该节点出口特性,非 relay 故障。 此类节点的 UDP 业务建议换用当地可达目标,或走其它节点。

客户端协议:认证与 TCP CONNECT 相同(TLS + Basic Auth),然后 CONNECT sp.v2.udp-over-tcp.arpa:443 HTTP/1.1,200 之后收发 UoT v2 帧: 请求头 | isConnect u8 | ATYP u8 | address | port u16be |, connect 模式帧 | length u16be | data |,non-connect 帧每帧自带目标地址。

5.6 CONNECT 超时口径(避免客户端"卡死")

三个超时要按"客户端最没耐心"来配:

项 值 说明
proxy_connect_connect_timeout 10s 出站连接目标;须 < 客户端超时(lite 客户端 15s)
proxy_connect_read_timeout / send_timeout 10m 隧道空闲超时(UoT 长连接需要;注意与 data_timeout 同槽位)

典型故障:目标 IP 是黑洞(不发 RST)时,若 nginx 出站超时设 60s 而客户端 15s 超时, 客户端会表现为 The read operation timed out 而不是收到 502 —— 看起来像"代理卡死", 实际是超时错配。2026-09 实测:改为 10s 后,黑洞目标从 >25s 降至 10.2s 返回, 正常目标(0.4~0.7s)不受影响。

排查"CONNECT 502"的正确顺序(重要):

  1. 先看目标是否本身就不可达——从多个网络(国内/日本/美国/香港)实测该 IP:端口, 若都失败则是目标问题(服务下线/过期 IP/黑洞),不是代理问题;
  2. 再看 nginx error.log:Connection refused / timed out / Network is unreachable 分别对应目标拒绝、目标黑洞、路由不可达;
  3. 最后才怀疑 CONNECT 模块本身。

客户端日志只记 IP 不记域名时,无法判断"是不是 DNS 过期解析"。 建议客户端日志同时记录原始域名(实测案例:某 Dropbox 旧 IP 已失效, 域名现已解析到其它地址,客户端仍在拨旧 IP)。

6. sing-box(VLESS+REALITY / Shadowsocks)

6.1 安装

SINGBOX_VER=1.13.19
curl -LO https://github.com/SagerNet/sing-box/releases/download/v$SINGBOX_VER/sing-box-$SINGBOX_VER-linux-amd64.tar.gz
tar xzf sing-box-*.tar.gz
install -m 755 sing-box-$SINGBOX_VER-linux-amd64/sing-box /usr/local/bin/sing-box

6.2 配置

完整配置见 sing-box/config.json。要点:

  • VLESS+REALITY:每节点独立 UUID/密钥对/short_id,SNI 全节点统一,flow xtls-rprx-vision;REALITY handshake server 用 <伪装SNI域名>:443
  • 纯 IPv4 节点(如 Azure 不给 IPv6 的 jp):direct 出站必须配 domain_resolver, 否则双栈域名会解析出 AAAA 并拨向不可达的 IPv6(日志刷 network is unreachable, jp 实测每小时数十条)。1.12+ 写法(legacy domain_strategy 字段在 sing-box 1.13 已直接 FATAL 拒绝,勿用):
    "dns": {"servers": [{"type": "local", "tag": "local-dns"}]},
    "outbounds": [{"type": "direct", "tag": "direct",
                   "domain_resolver": {"server": "local-dns", "strategy": "ipv4_only"}}]
    
    已验证:jp 需要(2026-10-01 已修);ru/hk2/us/aq 有 IPv6 出口或实测无此错误,不需要。
  • Shadowsocks:2022-blake3-aes-256-gcm(SIP022:会话子密钥 + 抗重放探测,显著优于老 AEAD aes-256-gcm)+ 每节点独立 PSK,独立端口(兼容不支持 REALITY 的老客户端)
    • PSK 格式要求:密码必须是 base64 编码的 32 字节密钥(openssl rand -base64 32),不是任意字符串;改错格式 sing-box 启动即报错
    • sing-box / Shadowrocket / mihomo 均支持 2022-blake3 系列
  • 无 SOCKS5 inbound(已按需移除;如需可在 inbounds 追加 type: socks)

6.3 REALITY 密钥生成

每台节点独立生成一套(互不共用):

# 生成私钥/公钥(sing-box 1.13+ 内置 reality 密钥生成)
sing-box generate reality-keypair
# 输出 private_key / public_key,记录备用
# short_id 用 8 字节随机 hex:
openssl rand -hex 8
# UUID:
sing-box generate uuid

私钥只放服务端配置;公钥给客户端订阅。UUID/密钥对/short_id 每节点独立。

公钥必须推导验证,禁止手抄。 订阅/节点配置里的 public_key 与服务端 private_key 必须严格配对(X25519 推导),曾发生手抄错一个字符导致全节点 REALITY 握手失败(客户端报 reality verification failed)的事故。改配置后验证:

python3 - << 'EOF'
import base64
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives import serialization
priv = base64.urlsafe_b64decode('<REALITY_PRIVATE_KEY>' + '==')
pub = X25519PrivateKey.from_private_bytes(priv).public_key().public_bytes(
    serialization.Encoding.Raw, serialization.PublicFormat.Raw)
print(base64.urlsafe_b64encode(pub).decode().rstrip('='))  # 应与下发的 public_key 逐字符一致
EOF

6.4 Hysteria2 / TUIC(UDP 传输)— ⚠️ 已回滚,本节为留档

状态:已回滚(2026-09-20 当晚)。 当日曾在五节点部署并逐组合端到端验证通过(见下表与「验证方法」), 随后因「绝对不可以被 GFW 封禁」这一优先级,全部撤除——五节点 sing-box 已恢复为 vless + shadowsocks 原状,新增的 UDP 入站不再监听。

本节保留作留档:配置片段、各节点 NAT/NSG 差异、端到端验证方法(尤其"只看监听不足以证明外部可达" 这个坑)在日后重新启用或换机时仍然有效。若要重新启用,照本节执行即可,但请先读 §2.6 的撤除理由。

TCP 系协议在跨境丢包链路上受拥塞控制限制(BBR 已缓解但仍有天花板);QUIC 系 (Hysteria2 的 Brutal CC、TUIC v5)能显著改善高丢包下的吞吐与时延。 两者都是 sing-box 内建,无需额外二进制;UDP 需求也可走 UoT(§5.5)。

// Hysteria2(sing-box inbound)
{ "type": "hysteria2", "tag": "hy2-in-<端口>", "listen": "0.0.0.0", "listen_port": "<端口>",
  "up_mbps": 100, "down_mbps": 100,
  "obfs": { "type": "salamander", "password": "<obfs密码>" },
  "users": [ { "name": "<节点>-hy2", "password": "<hy2密码>" } ],
  "tls": { "enabled": true, "server_name": "<节点域名>",
           "certificate_path": "/etc/nginx/ssl/<节点域名>/fullchain.pem",
           "key_path": "/etc/nginx/ssl/<节点域名>/privkey.pem", "alpn": ["h3"] },
  "masquerade": "file:///var/www/html" }

// TUIC v5
{ "type": "tuic", "tag": "tuic-in-<端口>", "listen": "0.0.0.0", "listen_port": "<端口>",
  "users": [ { "name": "<节点>-tuic", "uuid": "<UUID>", "password": "<密码>" } ],
  "congestion_control": "bbr",
  "tls": { /* 同上 */ } }

要点

  • 带宽声明决定 Brutal:up_mbps/down_mbps 不写时,服务端会命令客户端改用 BBR; 写了才允许 Hysteria CC(Brutal)。默认 100 Mbps,计量流量的节点给 50。 Brutal 不遵守拥塞控制,设错会占满链路甚至被服务商判定异常流量——务必按实际带宽设。
  • obfs: salamander 给 QUIC 加一层混淆(对抗 QUIC 特征识别);masquerade 指向伪装页, 未认证访问返回与 nginx 一致的页面。
  • TUIC 保持 zero_rtt_handshake 关闭(默认,0-RTT 可被重放)。
  • 凭据每节点独立,与 VLESS/SS/CONNECT 同策略。

各节点端口与部署差异(2026-09-20 实测,务必照抄结论)

节点 虚拟化 Hysteria2 (UDP) TUIC (UDP) 差异 / 坑
hk2 LXC(NAT) 4305 4306 NAT 段 4301-4320 UDP 1:1(探针 + 外部回扫实测)
us KVM 1208 1209 公网直连,UDP 任意端口可用
ru LXC(NAT) 23333 23334 外部只有宿主已映射的 UDP 端口可达:容器内 bind 任意端口都"正常",但 23336+ 实测直接 connection refused(ICMP port unreachable);最终复用 23333/23334——与 nginx/vless 占用的 TCP 同号,UDP 与 TCP 互不冲突
aq KVM(IPv6-only) 8444 8445 节点为 IPv6 出口,listen 用 "::";客户端须走 IPv6
jp Azure VM 未部署 8445 Azure NSG 只放行 UDP 8445(443/80/8080/8088/18080/8443/9444 的 UDP 面实测均不可达)→ 单节点只能承载一个 UDP 协议;要加 Hysteria2 须先在 NSG 放行第二个 UDP 端口

客户端链接格式(手工导入用)

hysteria2://<password>@<域名>:<端口>/?sni=<域名>&alpn=h3&obfs=salamander&obfs-password=<obfs密码>#备注
tuic://<uuid>:<password>@<域名>:<端口>/?congestion_control=bbr&alpn=h3&sni=<域名>#备注

接入现状(2026-09-20,务必先读)

层 状态
服务端 ⛔ 已撤除(原:五节点部署并端到端验证)——2026-09-20 当晚按抗封优先回滚
订阅下发 ❌ 从未下发(app.py 无 hysteria2/tuic 生成逻辑,nodes.json 无对应字段;订阅里只有 http / vless / ss),且已决定不做
代理组 ❌ 订阅只生成一个 RRPP(type: select,手动选节点),没有 fallback / url-test,因此不存在"UDP 挂了自动回落 TCP"
lite 客户端(Windows) 仅 HTTP CONNECT + UoT v2(UDP 走 magic 地址),不使用 UDP 入站,且节点为手动切换、无自动故障转移
结论 UDP 通道目前只能手工导入上面的链接使用;抗封主路径仍是 REALITY+Vision(TCP),UDP 属可选加速,未接入客户端链路

若日后要接入订阅,需要三步:① nodes.json 每节点补 methods.hysteria2 / methods.tuic; ② app.py 增加 mihomo type: hysteria2 / type: tuic 生成与分享链接; ③ 代理组改为每节点一个 fallback 组、REALITY 排第一(否则"UDP 挂了回落 TCP"不成立)。 注意 mihomo 的 fallback 依赖健康检查周期(默认 300s),切换不是瞬时的。

验证方法(端到端,不能只看监听)

# 在任意机器用 sing-box 作客户端:socks 入站 + 上述 outbound,然后
curl -s -m 20 -x socks5h://127.0.0.1:11080 -o /dev/null -w '%{http_code}\n' http://example.com/   # 期望 200

ss -lunp 看到端口在听不足以证明外部可达:ru(NAT 未映射)与 jp(NSG 未放行) 都是"监听正常但外部不可达"的真实案例。

⚠️ 代价与限制:UDP 在部分运营商会被 QoS/封禁;Brutal 对同链路其他流量不友好; 计量流量的节点(如 jp,15GB/月)注意消耗;LXC 容器内 socket 缓冲上限受宿主机约束。


7. mtg(MTProto 代理)

Telegram 内置代理,Fake TLS 模式抗封锁。用 mtg(Go 单二进制):

7.1 安装

MTG_VER=2.2.8
curl -LO https://github.com/9seconds/mtg/releases/download/v$MTG_VER/mtg-$MTG_VER-linux-amd64.tar.gz
tar xzf mtg-*.tar.gz
install -m 755 mtg-$MTG_VER-linux-amd64/mtg /usr/local/bin/mtg

7.2 生成 secret(每台独立)

mtg generate-secret --hex <伪装SNI域名>
# 输出 ee<32hex><域名hex>,以 ee 开头 = Fake TLS 类型

7.3 运行

mtg simple-run --prefer-ip=prefer-ipv4 0.0.0.0:<mtproto端口> <上面生成的secret>

--prefer-ip=prefer-ipv4:强制 IPv4 出站,避免容器/双栈环境下 IPv6 不可达导致回退失败。

7.4 Telegram 客户端参数

  • 服务器:<节点域名>
  • 端口:<mtproto端口>
  • Secret:上面生成的 hex(Telegram 自动识别 Fake TLS)

8. 本机公网 IP 自动识别(防自代理回环)

正向代理转发时,若目标 Host 是本机公网 IP,会被识别为"本机直连"返回伪装页(而非经代理转发本机,造成回环)。公网 IP 可能变动,用 cron 脚本每 5 分钟自动维护。

脚本见 scripts/sync-nginx-local-ips.sh,cron 见 cron/sync-nginx-local-ips:

*/5 * * * * root /usr/local/sbin/sync-nginx-local-ips.sh >/dev/null 2>&1

脚本逻辑:取 api.ipify.org 出站 IP + 节点域名 A 记录,比对重写 /etc/nginx/local_ip_rules.conf,仅变更时 nginx -t && systemctl reload nginx。


9. systemd 服务文件

服务 文件
nginx systemd/nginx.service
sing-box systemd/sing-box.service
mtg systemd/mtg.service
uot-relay uot-relay/uot-relay.service
rrpp-qdisc(fq 队列,见 2.6) systemd/rrpp-qdisc.service
cp systemd/*.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now nginx sing-box mtg

10. 启动顺序与验证清单

启动顺序

sysctl --system                      # 1. BBR
systemctl start sing-box             # 2. VLESS/SS
systemctl start mtg                  # 3. MTProto
systemctl start nginx                # 4. nginx(最后,依赖证书/伪装页/规则文件)
systemctl start uot-relay              # 5. UoT relay(须在 nginx 前可用)

验证清单

# BBR
sysctl net.ipv4.tcp_congestion_control   # => bbr
sysctl net.ipv4.tcp_mtu_probing          # => 1(PMTUD 黑洞缓解,见 2.4)

# 系统级网络优化(见 2.6):确认 sysctl.d 的值真的生效(注意 /etc/sysctl.conf 覆盖坑)
sysctl -n net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_fastopen \
          net.ipv4.tcp_slow_start_after_idle net.ipv4.ip_local_port_range
# KVM/完整 VM 追加:net.core.netdev_max_backlog / rmem_max / wmem_max
tc qdisc show | grep -v noqueue         # 期望默认路由网卡为 fq(容器内为 noqueue,属正常)
systemctl is-active rrpp-qdisc          # 期望 active(容器内该单元会自行跳过)

# fail2ban
fail2ban-client status sshd              # => jail 存在(见 2.5)

# sing-box
sing-box check -c /etc/sing-box/config.json
systemctl is-active sing-box

# mtg(Fake TLS:非法 ClientHello 应返回 TLS alert 0x15)
timeout 8 python3 -c "
import socket,os,struct
s=socket.connect(('<节点域名>',<mtproto端口>)) if False else socket.create_connection(('<节点域名>',<mtproto端口>),timeout=6)
s.sendall(b'\x16\x03\x01'+struct.pack('>H',100)+os.urandom(100))
print('resp:',s.recv(8).hex())"   # 期望首字节 15 (TLS alert)

# UoT relay(127.0.0.1:15201 监听 + CONNECT magic 地址)
ss -tlnp | grep 15201   # => uot_relay.py 监听 127.0.0.1:15201

# UDP 传输(Hysteria2/TUIC)——已于 2026-09-20 撤除;以下仅当重新启用时才适用
# 必须端到端验证,不能只看监听
ss -lunp | grep -E ':<hy2端口>|:<tuic端口>'   # 仅确认服务在听
# 再用 sing-box 客户端 + curl 经由 socks 实测(见 6.4「验证方法」)
ethtool -k <网卡> | grep -E 'rx-udp-gro-forwarding|rx-gro-list'   # KVM 期望 on;LXC 容器不可设

# nginx CONNECT(带 Basic Auth,B64 = base64('<代理用户名>:<代理密码>'))
B64=$(printf '<代理用户名>:<代理密码>' | base64 -w0)
curl -sk --proxy-insecure -x https://<节点域名>:<nginx端口> \
  --proxy-header "Authorization: Basic $B64" \
  -o /dev/null -w '%{http_code}\n' http://example.com/    # => 200

# 伪装页(直连 IP/域名,无认证)
curl -sk https://<节点域名>:<nginx端口>/ -o /dev/null -w '%{http_code}\n'   # => 200

11. 目录结构与文件索引

server_config/
├── README.md                        ← 本文档
├── sysctl/
│   ├── 99-zz-rrpp-network.kvm.conf  ← 系统级网络优化(KVM/完整 VM,见 §2.6)
│   └── 99-zz-rrpp-network.lxc.conf  ← 系统级网络优化(LXC/OpenVZ 容器,见 §2.6)
├── nginx/
│   ├── nginx.conf                   ← 完整 nginx 配置(占位符版)
│   ├── local_ip_rules.conf.example  ← 本机 IP 规则文件示例(cron 自动生成,勿手改)
│   └── conf.d/                      ← 预留:额外站点配置
├── uot-relay/
│   ├── uot_relay.py                   ← UoT v2 relay(UDP over TCP 解封装)
│   └── uot-relay.service              ← systemd unit
├── sing-box/
│   └── config.json                  ← sing-box 配置(VLESS+REALITY + SS,占位符版)
├── systemd/
│   ├── nginx.service
│   ├── sing-box.service
│   ├── mtg.service
│   └── rrpp-qdisc.service           ← 开机把默认网卡 qdisc 设为 fq(见 §2.6)
├── scripts/
│   ├── sync-nginx-local-ips.sh      ← 本机公网 IP 规则自动维护
│   └── rrpp-qdisc.sh                ← fq 队列设置脚本(rrpp-qdisc.service 调用)
├── cron/
│   └── sync-nginx-local-ips         ← cron 定义(每 5 分钟)
├── rrpp-subscription/               ← RRPP 订阅后端(bj,见 §12)
│   ├── API.md                       ← 订阅端点 / method / 分层 / 规则集文档
│   └── gen_rulesets.py              ← PAC → 客户端规则集转换脚本
└── fake-site/
    └── index.html                   ← 伪装站点页面(所有节点共用同一份)

旧的 traffic-monitor/、pac/、conf.d/*.conf 已下线或迁移,不再维护,已从本目录移除。


12. RRPP 订阅后端(PAC → 规则集下发)

订阅服务运行在 bj(C:/BtSoft/nginx/others/backEnd/api-rrrosmontis-icu/RRPP/,nssm 服务 RRPPSubBackend,监听 127.0.0.1:5010,由 nginx api.rrrosmontis.icu 反代)。 完整端点说明见 rrpp-subscription/API.md(bj 上同名文件为主副本,两处内容保持一致)。

12.1 PAC 为什么不能直接下发

PAC 是浏览器/系统用的 JS 自动配置,mihomo/Clash 与 Shadowrocket 都读不了。两者唯一共同能吃的格式是 「每行自带规则类型的规则行文本」(Clash 的 behavior: classical):

  • mihomo → rule-providers(behavior: classical + format: text)
  • Shadowrocket → RULE-SET,<url>,<策略>(官方手册:规则集内容需包含规则类型)

因此由后端 gen_rulesets.py 把 PAC 数据转成三个 .list,两种客户端共用同一份文件。

12.2 规则集产物

规则集 文件(https://cdn.rrrosmontis.icu/RRPP/PAC/) 规则数 策略
rrpp-proxy rrpp-proxy.list 3943(DOMAIN 539 + DOMAIN-SUFFIX 3404) RRPP / PROXY
rrpp-direct rrpp-direct.list 16(DOMAIN-SUFFIX) DIRECT
rrpp-cn-ip rrpp-cn-ip.list 7456(IP-CIDR) DIRECT

顺序(与 PAC 语义一致):强制代理 → 直连种子 → 中国 IP 直连 → MATCH 全代理。

12.3 维护:PAC 更新后重新生成

# bj 上执行(Windows:C:/BtSoft/nginx/others/htmlResources/cdn/RRPP/PAC/ 为生成目录,同时即 CDN 根)
python gen_rulesets.py            # 幂等:内容无变化则不写盘;原子替换
python gen_rulesets.py --check    # 只预览差异,不写盘

产物文件名与 URL 固定,订阅端与客户端均无需改动:mihomo 按 interval: 86400 自动更新, Shadowrocket 在「配置 → 规则集URL」中自动更新。

12.4 客户端接入

# mihomo / Clash Verge:规则走 rule-providers(YAML 约 2.4KB)
...&method=clash
# 不认 rule-providers,或要「导入一次到位」:规则内联(约 440KB)
...&method=clash&rules=inline
# Shadowrocket:只下发规则([General] + 远程 RULE-SET + FINAL,不含节点)
...&method=shadowrocket
# 兜底:全流量走代理(旧行为)
...&method=clash&rules=global

method=shadowrocket 的 .conf 不写 [Proxy] 段——Shadowrocket 的 REALITY 参数写法官方未文档化,不猜测; 策略 PROXY 指向首页当前选中的节点,节点用 clash/v2ray 订阅导入。 想在小火箭里「一次导入节点+规则」,用 method=clash&rules=inline。

12.5 验证口径

  • 生成:11415 条规则(regexp: 跳过 7、非法 1)
  • CDN:三个 URL HTTP 200,md5 与本地一致(806808b8 / 8f89397e / 3e75093d)
  • mihomo v1.19.31 真实内核:-t 通过;运行时 GET /providers/rules 报 3943 / 16 / 7456(behavior=Classical), 落地文件字节数与生成一致;rules=inline 亦通过 -t
  • Shadowrocket 为 iOS 应用,未做实机验证(依官方手册 RULE-SET 与 Clash YAML 兼容说明实现)