Debian & Ubuntu 搭建部署 sing-box 的 VLESS + HTTPUpgrade + Cloudflare Tunnel + ECH + Caddy 服务

Contents
  1. 1. 背景与原理
    1. 1.1. 整体架构
    2. 1.2. 关于 TLS-in-TLS
    3. 1.3. 本方案不适用的场景
  2. 2. 准备工作
    1. 2.1. 所需资源
    2. 2.2. 安装所需基本工具
  3. 3. Cloudflare Tunnel 配置
    1. 3.1. VPS 安装并配置 cloudflared
    2. 3.2. 创建 Tunnel
    3. 3.3. 配置 Public Hostname
    4. 3.4. SSL/TLS 设置
    5. 3.5. 确认 ECH 状态
  4. 4. VPS 部署
    1. 4.1. 安装 Caddy
    2. 4.2. 安装 sing-box
    3. 4.3. 生成 UUID
  5. 5. 伪装站建设
  6. 6. 服务端配置文件
    1. 6.1. 生成隐藏路径
    2. 6.2. Caddy 配置
    3. 6.3. sing-box 配置
  7. 7. 验证服务端
    1. 7.1. 服务状态检查
    2. 7.2. 浏览器访问检查
    3. 7.3. 确认 ECH 可用
  8. 8. 客户端配置文件
    1. 8.1. 客户端关键字段说明
    2. 8.2. 使用优选 Cloudflare IP
  9. 9. 隐蔽性评估
  10. 10. 维护与长期可用性
    1. 10.1. 更新组件
    2. 10.2. 定期轮换
    3. 10.3. 常见排查
  11. 11. References

本文最后更新于 2026 年 4 月 22 日


本文以 Debian 13 为例,介绍如何搭建 sing-box 的 VLESS + HTTPUpgrade 服务端,通过 Cloudflare Tunnel 隐藏源站 IP,使用 Caddy 建立伪装站,并在客户端启用 ECH 加密 SNI。本文同样完全适用于 Ubuntu 24.04 及以上系统。

本方案有以下特点:

  1. VPS 不开放任何公网入站端口(防火墙仅允许 SSH 和出站),真实 IP 完全不出现在 DNS 记录、证书或任何公开信息中
  2. 无需申请 TLS 证书(外层 TLS 由 Cloudflare 边缘节点处理,VPS 内部全程明文 HTTP 回环通信)
  3. 浏览器直接访问域名显示真实的伪装网站,代理路径隐藏在特定 URI 下
  4. 客户端启用 ECH 后,中间人仅能看到 cloudflare-ech.com 的外层 SNI,无法得知实际访问的域名

注:请先参照 Debian & Ubuntu 服务器的初始化配置 一文对服务器进行各种必要的配置。本文以 sammy 用户为例,并默认已按初始化配置文章对服务器进行了配置。


背景与原理

整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
客户端 sing-box (VLESS + HTTPUpgrade)
│ TLS 1.3 + ECH(外层 SNI: cloudflare-ech.com)

Cloudflare 边缘节点
│ CF 内部协议(Tunnel)

VPS 上 cloudflared 守护进程(主动出站长连接)
│ 明文 HTTP(本机回环)

Caddy(127.0.0.1:8080)
├─ 其他请求 → 伪装静态网站
└─ /你的隐藏路径 + Upgrade 头 → sing-box VLESS inbound


sing-box(127.0.0.1:8443)→ direct outbound → 目标网站

关于 TLS-in-TLS

本方案中,用户访问 HTTPS 网站时产生的内层 TLS 流量被封装在 Cloudflare 的外层 TLS 连接中,TLS-in-TLS 特征不可避免。sing-box 的 multiplex.padding 可在多路复用层添加随机填充以缓解长度特征,但无法完全消除。需要消除 TLS-in-TLS 的场景应选择 AnyTLS + REALITY 或者 VLESS + Vision + REALITY(两者都是直连方案,无法过 CDN)。

本方案不适用的场景

  1. VPS 无法出站连接 Cloudflare 的场景(极少数网络环境)
  2. 需要完全消除 TLS-in-TLS 特征的场景
  3. 追求极致低延迟的场景(CF Tunnel 多一跳,延迟略高于直连)

准备工作

所需资源

  1. 一台 VPS(任何地区均可,NAT VPS 也可以,只要能出站连接 Cloudflare)
  2. 一个域名(已托管到 Cloudflare,免费域名即可)
  3. 一套静态网页内容(推荐,作为伪装站)

下文假设域名为 example.com,代理用子域名 cdn.example.com

安装所需基本工具

1
sudo apt update && sudo apt install curl vim wget ca-certificates unzip -y

Cloudflare Tunnel 配置

VPS 安装并配置 cloudflared

1
2
3
4
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared

创建 Tunnel

  1. 登录 dash.cloudflare.com
  2. 左侧菜单选择 Zero Trust
  3. 左侧菜单 Networks → Connectors
  4. 点击 Create a tunnel,选择 Cloudflared
  5. Tunnel 名称随意填写,点击 Save tunnel
  6. 下一步会显示安装命令,复制此命令,在 VPS 上执行。

运行复制的命令注册并启动服务:

1
sudo cloudflared service install eyJhIjoiYWJjZGVmMTIzNDU2Nzg5...

确认运行状态:

1
sudo systemctl status cloudflared

并可在 Cloudflare 网页控制台看到 Connector 已经建立连接,然后点击 Next

配置 Public Hostname

在页面如此配置:

字段
Subdomaincdn
Domainexample.com
Path(留空)
TypeHTTP
URLlocalhost:8080

然后点击 Complete Setup 保存后,Cloudflare 会自动在 DNS 中创建 CNAME 记录 cdn.example.com<tunnel-id>.cfargotunnel.com

SSL/TLS 设置

进入 Cloudflare 域名 Dashboard → SSL/TLS:

  • Overview → 加密模式设为 Full (strict)(Tunnel 模式下 CF 不经过传统 HTTPS 回源,此设置不影响 Tunnel 通信,选择最严格的模式即可)
  • Edge Certificates → Minimum TLS Version:TLS 1.3
  • Edge Certificates → TLS 1.3:On

确认 ECH 状态

进入 SSL/TLS → Edge Certificates,找到 Encrypted ClientHello (ECH)。Free 计划域名默认已启用。其他计划确认状态为 Enabled 即可。ECH 的密钥管理与 ECHConfig 发布由 Cloudflare 自动完成,服务端无需任何操作。


VPS 部署

安装 Caddy

1
2
3
4
5
6
7
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

安装 sing-box

sing-box 官方提供了 APT 仓库,推荐优先使用此方式,便于后续升级。

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta

验证版本:

1
sing-box version

生成 UUID

1
sing-box generate uuid

记录输出的 UUID,服务端与客户端均需使用。


伪装站建设

伪装站的质量直接影响方案的隐蔽性。推荐使用 Hugo 或 Hexo 生成的个人技术博客、摄影作品集、小工具站等。避免使用空白页、默认欢迎页或任何敏感内容。

快速准备一个最简伪装页(注意替换 sammy 为实际用户名)::

1
2
sudo mkdir -p /var/www/disguise
sudo chown -R sammy:sammy /var/www/disguise

创建 /var/www/disguise/index.html,内容为任意看起来合理的静态网页。如有 Hugo 等静态站生成器,将生成的 public/ 目录内容放入 /var/www/disguise/ 中。


服务端配置文件

生成隐藏路径

1
openssl rand -hex 16

输出示例:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6。将其作为代理路径使用,使用时前面注意加斜杠。

Caddy 配置

编辑 /etc/caddy/Caddyfile,注意替换其中的随机隐藏路径:

1
sudo vim /etc/caddy/Caddyfile
/etc/caddy/Caddyfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
admin off
auto_https off
}

:8080 {
root * /var/www/disguise

@proxy {
path /之前生成的随机隐藏路径
header Connection *Upgrade*
}
reverse_proxy @proxy 127.0.0.1:8443

file_server {
index index.html
}

handle_errors {
rewrite * /404/index.html
file_server
}

encode gzip zstd

log {
output file /var/log/caddy/access.log {
roll_size 10mb
roll_keep 3
}
level ERROR
}
}

auto_https off:TLS 由 Cloudflare 处理,Caddy 不需要管理证书。@proxy matcher 同时检查路径与 Connection: Upgrade 头,浏览器直接访问该路径(没有 Upgrade 头)会落入 file_server 返回伪装页面。

验证并重启:

1
2
3
sudo mkdir -p /var/log/caddy && sudo chown -R caddy:caddy /var/log/caddy
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy

sing-box 配置

编辑 /etc/sing-box/config.json注意替换 你生成的 UUID 和路径

1
sudo vim /etc/sing-box/config.json
/etc/sing-box/config.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{
"log": {
"level": "warn",
"timestamp": true
},
"dns": {
"servers": [
{
"type": "https",
"tag": "local",
"server": "1.1.1.1"
}
],
"strategy": "prefer_ipv4"
},
"inbounds": [
{
"type": "vless",
"tag": "vless-in",
"listen": "127.0.0.1",
"listen_port": 8443,
"users": [
{
"name": "sammy",
"uuid": "你生成的 UUID"
}
],
"transport": {
"type": "httpupgrade",
"path": "/之前生成的随机隐藏路径"
},
"multiplex": {
"enabled": true,
"padding": true
}
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct",
"domain_resolver": "local"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"ip_is_private": true,
"action": "reject"
},
{
"protocol": "bittorrent",
"action": "reject"
}
],
"final": "direct"
}
}

关键字段说明:

inbounds[].listen127.0.0.1,仅接收 Caddy 反代过来的请求,不监听公网。

inbounds[].transport.typehttpupgradesing-box 推荐的 CDN 传输方式。注意此处不配置 flow 字段——Vision 流控要求底层裸 TCP,与任何 transport 不兼容。

inbounds[].transport.path:必须与 Caddy 配置中的路径完全一致。

验证并启动:

1
2
sudo sing-box check -c /etc/sing-box/config.json
sudo systemctl enable --now sing-box

验证服务端

服务状态检查

1
2
3
sudo systemctl status cloudflared   # active (running)
sudo ss -tlnp | grep 8080 # 127.0.0.1:8080 Caddy
sudo ss -tlnp | grep 8443 # 127.0.0.1:8443 sing-box

浏览器访问检查

  1. 访问 https://cdn.example.com → 应显示伪装网站
  2. 访问 https://cdn.example.com/任意路径 → 应显示伪装网站
  3. 访问 https://cdn.example.com/之前生成的随机隐藏路径 → 应显示伪装网站(浏览器不发送 Upgrade 头,Caddy 不会转发到 sing-box)

三项均符合预期,说明伪装站与路径隐蔽性到位。

确认 ECH 可用

在本地终端(非 VPS)执行:

1
dig cdn.example.com HTTPS @1.1.1.1

返回结果中包含 ech=... 字段,说明 Cloudflare 已发布 ECHConfig,客户端可以使用 ECH。


客户端配置文件

以下为客户端示例配置,注意替换 你生成的 UUID 和路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
{
"log": {
"level": "warn",
"timestamp": true
},
"dns": {
"servers": [
{
"type": "https",
"tag": "remote",
"server": "1.1.1.1",
"detour": "proxy"
},
{
"type": "udp",
"tag": "local",
"server": "223.5.5.5"
}
],
"rules": [
{
"rule_set": "geosite-cn",
"server": "local"
}
],
"final": "remote",
"strategy": "prefer_ipv4"
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"address": [
"172.19.0.1/30",
"fdfe:dcba:9876::1/126"
],
"auto_route": true,
"strict_route": true
}
],
"outbounds": [
{
"type": "selector",
"tag": "proxy",
"outbounds": [
"auto",
"cf-main"
],
"default": "auto"
},
{
"type": "urltest",
"tag": "auto",
"outbounds": [
"cf-main"
],
"url": "http://www.gstatic.com/generate_204",
"interval": "3m"
},
{
"type": "vless",
"tag": "cf-main",
"server": "cdn.example.com",
"server_port": 443,
"uuid": "你生成的 UUID",
"tls": {
"enabled": true,
"server_name": "cdn.example.com",
"alpn": [
"http/1.1"
],
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"ech": {
"enabled": true
}
},
"transport": {
"type": "httpupgrade",
"host": "cdn.example.com",
"path": "/之前生成的随机隐藏路径"
},
"multiplex": {
"enabled": true,
"protocol": "h2mux",
"max_streams": 16,
"padding": true
}
},
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"default_domain_resolver": "local",
"rules": [
{
"action": "sniff"
},
{
"protocol": "dns",
"action": "hijack-dns"
},
{
"ip_is_private": true,
"outbound": "direct"
},
{
"rule_set": "geosite-cn",
"outbound": "direct"
},
{
"rule_set": "geoip-cn",
"outbound": "direct"
}
],
"rule_set": [
{
"type": "remote",
"tag": "geosite-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
"download_detour": "proxy"
},
{
"type": "remote",
"tag": "geoip-cn",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
"download_detour": "proxy"
}
],
"final": "proxy",
"auto_detect_interface": true
},
"experimental": {
"cache_file": {
"enabled": true
}
}
}

客户端关键字段说明

outbounds[].tls.alpn:固定为 ["http/1.1"]。HTTPUpgrade 基于 HTTP/1.1 的 Upgrade 机制,不能使用 h2。

outbounds[].tls.utls:启用 uTLS 伪造 ClientHello 指纹。推荐 chrome,与大多数实际浏览器流量分布一致。

outbounds[].tls.ech:启用 Encrypted ClientHello。sing-box 会自动通过 DNS HTTPS RR 记录获取 ECHConfig。启用后,中间人看到的外层 SNI 为 cloudflare-ech.com,真实的 cdn.example.com 被加密保护。要求客户端的 DNS 服务器支持 HTTPS RR 查询(Cloudflare 1.1.1.1、Google 8.8.8.8 等均支持)。

outbounds[].transport.typehttpupgrade,与服务端一致。

outbounds[].multiplex:启用多路复用。protocol 使用 sing-box 默认的 h2mux(基于 HTTP/2 帧格式,性能与流控最优)。padding: true 在多路复用层添加随机填充,缓解 TLS-in-TLS 的长度特征。

使用优选 Cloudflare IP

如果域名直连 cdn.example.com 延迟较高,可添加多个使用优选 IP 的出站节点。方法是复制 cf-main 的配置,将 server 字段替换为优选 IP(使用 CloudflareSpeedTest 在本地测试获取),保持 tls.server_nametransport.host 不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"type": "vless",
"tag": "cf-ip-1",
"server": "104.16.132.229",
"server_port": 443,
"uuid": "你生成的 UUID",
"tls": {
"enabled": true,
"server_name": "cdn.example.com",
"alpn": [
"http/1.1"
],
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"ech": {
"enabled": true
}
},
"transport": {
"type": "httpupgrade",
"host": "cdn.example.com",
"path": "/之前生成的随机隐藏路径"
},
"multiplex": {
"enabled": true,
"protocol": "h2mux",
"max_streams": 16,
"padding": true
}
}

将新节点的 tag 加入 urltestselectoroutbounds 数组即可。


隐蔽性评估

开启 ECH 后,检测者视角:

检测维度看到的内容
连接目标 IPCloudflare 的 Anycast IP
外层 SNIcloudflare-ech.com(所有启用 ECH 的 CF 站点共用)
真实域名加密,不可见
证书Cloudflare 签发的 cloudflare-ech.com 证书
VPS 真实 IP不可见(Tunnel 内部通信)
浏览器探测域名正常伪装网站
路径扫描所有路径均返回伪装页面

残留风险:TLS-in-TLS 特征(被 multiplex.padding 缓解但未消除)、流量行为特征(持续连接时长、请求频率模式)。


维护与长期可用性

更新组件

1
2
sudo apt update && sudo apt upgrade cloudflared sing-box caddy -y
sudo systemctl restart cloudflared sing-box caddy

定期轮换

建议每 3-6 个月轮换 UUID 与路径:

1
2
sing-box generate uuid
openssl rand -hex 16

同时更新服务端(sing-box + Caddy)与客户端配置。

常见排查

  1. 浏览器访问域名显示 Cloudflare 错误页(1033 等):Tunnel 未连接,检查 systemctl status cloudflared
  2. 浏览器显示 502:Caddy 未运行或端口配置错误
  3. 客户端连接超时:确认 UUID、路径与服务端完全一致;确认 tls.server_nametransport.host 都填写了正确的域名
  4. ECH 未生效(抓包仍看到明文 SNI):确认客户端 DNS 能查到 HTTPS RR 记录(dig cdn.example.com HTTPS @1.1.1.1),确认返回中包含 ech=... 字段

References

sing-box

sing-box - VLESS Inbound

sing-box - HTTPUpgrade Transport

sing-box - Dial Fields

Cloudflare Tunnel

Cloudflare - Encrypted ClientHello

Caddy

XIU2 / CloudflareSpeedTest

Mastodon