- 2.4 节:客户端路径 PMTUD 黑洞导致「TCP 通但 TLS 握手稳定超时」, 节点开启 net.ipv4.tcp_mtu_probing=1(本次已在 us/ru/aq 落实,jp 原有) - 6.3 节:public_key 必须由 private_key 做 X25519 推导逐字符验证,禁止手抄 (手抄错 1 字符曾致全节点 REALITY 握手失败) - 验证清单补充 mtu_probing 检查项
425 lines
16 KiB
Markdown
425 lines
16 KiB
Markdown
# RRPP 服务端部署规范
|
||
|
||
> 一套统一的跨境代理节点部署流程,适用于 Debian/Ubuntu 系 Linux。
|
||
> 每台节点运行相同的服务栈:**BBR 拥塞控制 + nginx CONNECT 隧道 + sing-box(VLESS+REALITY / Shadowsocks) + mtg(MTProto)**。
|
||
>
|
||
> 本文档为**通用规范**,所有 IP / 域名 / 端口 / 密钥均以**占位符**表示,部署时替换为实际值。
|
||
> 凡标注 `<...>` 的均为占位符,不要原样照抄。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [0. 架构总览](#0-架构总览)
|
||
- [1. 端口与服务规划](#1-端口与服务规划)
|
||
- [2. 系统基础优化(BBR 跨境丢包优化)](#2-系统基础优化bbr-跨境丢包优化)
|
||
- [3. 伪装站点页面](#3-伪装站点页面)
|
||
- [4. HTTPS 证书(acme.sh DNS-01)](#4-https-证书acmesh-dns-01)
|
||
- [5. nginx 源码编译 + CONNECT 隧道](#5-nginx-源码编译--connect-隧道)
|
||
- [6. sing-box(VLESS+REALITY / Shadowsocks)](#6-sing-boxvlessreality--shadowsocks)
|
||
- [7. mtg(MTProto 代理)](#7-mtgmtproto-代理)
|
||
- [8. 本机公网 IP 自动识别(防自代理回环)](#8-本机公网-ip-自动识别防自代理回环)
|
||
- [9. systemd 服务文件](#9-systemd-服务文件)
|
||
- [10. 启动顺序与验证清单](#10-启动顺序与验证清单)
|
||
- [11. 目录结构与文件索引](#11-目录结构与文件索引)
|
||
|
||
---
|
||
|
||
## 0. 架构总览
|
||
|
||
```
|
||
客户端 ──► nginx(443/<nginx端口>) ──┬─► CONNECT 隧道(任意端口, 全端口放行)
|
||
│ └─► 伪装页面(直接访问/IP访问)
|
||
│
|
||
├─► sing-box(<vless端口>) ──► VLESS+REALITY(vision)
|
||
├─► sing-box(<ss端口>) ──► Shadowsocks(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 的客户端 |
|
||
| Telegram 专用 | mtg | MTProto Fake TLS | Telegram 内置代理,抗封锁 |
|
||
|
||
**核心密钥体系(全套节点共用一套):**
|
||
- VLESS+REALITY:统一 UUID、统一 REALITY 私钥/short_id/SNI(SNI 用 `<伪装SNI域名>`,选一个高流量、TLS 指纹稳定的知名站点域名)
|
||
- Shadowsocks:统一密码,方法 `aes-256-gcm`
|
||
- MTProto:每台**独立 secret**(Fake TLS 类型,fronting 到 `<伪装SNI域名>`)
|
||
|
||
---
|
||
|
||
## 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`:
|
||
|
||
```conf
|
||
net.core.default_qdisc=fq
|
||
net.ipv4.tcp_congestion_control=bbr
|
||
```
|
||
|
||
应用并验证:
|
||
|
||
```bash
|
||
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
|
||
|
||
代理证书校验、日志排障都依赖准确时间:
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```conf
|
||
net.ipv4.tcp_mtu_probing = 1
|
||
```
|
||
|
||
```bash
|
||
sysctl --system
|
||
sysctl net.ipv4.tcp_mtu_probing # 期望输出: 1
|
||
```
|
||
|
||
> 只影响命中黑洞的连接,正常连接无感知,可安全全节点开启。
|
||
|
||
---
|
||
|
||
## 3. 伪装站点页面
|
||
|
||
直接访问节点/IP 时返回一个普通网页(而非代理错误),降低被探测的特征。
|
||
|
||
放置一个静态 `index.html` 到 `/var/www/html/index.html`:
|
||
- 任意抓取的合法 HTML 页面(博客/门户首页快照均可)
|
||
- **所有节点用同一份**(md5 一致),避免逐节点特征差异
|
||
|
||
```bash
|
||
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](https://github.com/acmesh-official/acme.sh) + DNS-01(以阿里云 DNS 为例,其他 DNS 商同理):
|
||
|
||
```bash
|
||
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](https://github.com/chobits/ngx_http_proxy_connect_module) 补丁后**源码编译**。
|
||
|
||
### 5.1 编译
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
apt install -y apache2-utils
|
||
htpasswd -bc /etc/nginx/.htpasswd <代理用户名> '<代理密码>'
|
||
chmod 644 /etc/nginx/.htpasswd # worker 以 nobody 运行,必须可读
|
||
```
|
||
|
||
### 5.3 nginx.conf
|
||
|
||
完整配置见 [nginx/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
|
||
|
||
---
|
||
|
||
## 6. sing-box(VLESS+REALITY / Shadowsocks)
|
||
|
||
### 6.1 安装
|
||
|
||
```bash
|
||
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](sing-box/config.json)。要点:
|
||
- **VLESS+REALITY**:统一 UUID、统一私钥/short_id/SNI,flow `xtls-rprx-vision`;REALITY handshake server 用 `<伪装SNI域名>:443`
|
||
- **Shadowsocks**:`aes-256-gcm` + 统一密码,独立端口(兼容不支持 REALITY 的老客户端)
|
||
- 无 SOCKS5 inbound(已按需移除;如需可在 inbounds 追加 `type: socks`)
|
||
|
||
### 6.3 REALITY 密钥生成
|
||
|
||
首次部署生成一套(**全套节点共用**):
|
||
|
||
```bash
|
||
# 生成私钥/公钥(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`)的事故。改配置后验证:
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## 7. mtg(MTProto 代理)
|
||
|
||
Telegram 内置代理,Fake TLS 模式抗封锁。用 [mtg](https://github.com/9seconds/mtg)(Go 单二进制):
|
||
|
||
### 7.1 安装
|
||
|
||
```bash
|
||
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(每台独立)
|
||
|
||
```bash
|
||
mtg generate-secret --hex <伪装SNI域名>
|
||
# 输出 ee<32hex><域名hex>,以 ee 开头 = Fake TLS 类型
|
||
```
|
||
|
||
### 7.3 运行
|
||
|
||
```bash
|
||
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](scripts/sync-nginx-local-ips.sh),cron 见 [cron/sync-nginx-local-ips](cron/sync-nginx-local-ips):
|
||
|
||
```cron
|
||
*/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](systemd/nginx.service) |
|
||
| sing-box | [systemd/sing-box.service](systemd/sing-box.service) |
|
||
| mtg | [systemd/mtg.service](systemd/mtg.service) |
|
||
|
||
```bash
|
||
cp systemd/*.service /etc/systemd/system/
|
||
systemctl daemon-reload
|
||
systemctl enable --now nginx sing-box mtg
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 启动顺序与验证清单
|
||
|
||
### 启动顺序
|
||
|
||
```bash
|
||
sysctl --system # 1. BBR
|
||
systemctl start sing-box # 2. VLESS/SS
|
||
systemctl start mtg # 3. MTProto
|
||
systemctl start nginx # 4. nginx(最后,依赖证书/伪装页/规则文件)
|
||
```
|
||
|
||
### 验证清单
|
||
|
||
```bash
|
||
# BBR
|
||
sysctl net.ipv4.tcp_congestion_control # => bbr
|
||
sysctl net.ipv4.tcp_mtu_probing # => 1(PMTUD 黑洞缓解,见 2.4)
|
||
|
||
# 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)
|
||
|
||
# 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 ← 本文档
|
||
├── nginx/
|
||
│ ├── nginx.conf ← 完整 nginx 配置(占位符版)
|
||
│ ├── local_ip_rules.conf.example ← 本机 IP 规则文件示例(cron 自动生成,勿手改)
|
||
│ └── conf.d/ ← 预留:额外站点配置
|
||
├── sing-box/
|
||
│ └── config.json ← sing-box 配置(VLESS+REALITY + SS,占位符版)
|
||
├── systemd/
|
||
│ ├── nginx.service
|
||
│ ├── sing-box.service
|
||
│ └── mtg.service
|
||
├── scripts/
|
||
│ └── sync-nginx-local-ips.sh ← 本机公网 IP 规则自动维护
|
||
├── cron/
|
||
│ └── sync-nginx-local-ips ← cron 定义(每 5 分钟)
|
||
└── fake-site/
|
||
└── index.html ← 伪装站点页面(所有节点共用同一份)
|
||
```
|
||
|
||
> 旧的 `traffic-monitor/`、`pac/`、`conf.d/*.conf` 已下线或迁移,不再维护,已从本目录移除。
|