Debian & Ubuntu 搭建部署 Xray 的 VLESS + XHTTP + TLS + Nginx 并使用 Cloudflare

Contents
  1. 1. 准备工作
    1. 1.1. 所需资源
  2. 2. Cloudflare 设置
    1. 2.1. DNS
    2. 2.2. SSL/TLS 与 Network
    3. 2.3. Cache Rules
    4. 2.4. WAF Skip Rule
  3. 3. VPS 初始化
  4. 4. 服务器防火墙
  5. 5. 安装 Nginx
  6. 6. 安装 Xray-core
  7. 7. 申请证书
    1. 7.1. 切换到 root
    2. 7.2. 安装 acme.sh
    3. 7.3. 准备 Cloudflare API Token
    4. 7.4. 签发并安装证书
  8. 8. 伪装站
  9. 9. Nginx 配置
  10. 10. Xray 服务端配置
  11. 11. 客户端配置
    1. 11.1. ECH 策略
    2. 11.2. 使用 Cloudflare 优选 IP
  12. 12. 验证
    1. 12.1. 服务状态
    2. 12.2. 浏览器访问
    3. 12.3. 检查 ECHConfig
  13. 13. 常见问题
    1. 13.1. Cloudflare 返回 403
    2. 13.2. Cloudflare 返回 525 / 526
    3. 13.3. 浏览器正常,客户端不通
    4. 13.4. mode: auto 不稳定
    5. 13.5. ECH 导致连接失败
    6. 13.6. v26 padding 字段报错
  14. 14. 更新维护
    1. 14.1. 更新系统组件
    2. 14.2. 更新 Xray
    3. 14.3. 手动续期测试
    4. 14.4. 轮换 UUID、路径和 Token
  15. 15. 最终配置摘要
  16. 16. References

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


本文以 Debian 13 为例,介绍如何搭建 Xray-core 的 VLESS + XHTTP 服务端,使用 Nginx 反代,并通过 Cloudflare 橙色云隐藏源站 IP。本文同样适用于 Ubuntu 24.04 及以上系统。

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

本方案采用:

1
2
3
4
客户端 Xray
→ Cloudflare 橙色云
→ Nginx TLS + HTTP/2
→ Xray VLESS + XHTTP

本方案特点:

  1. XHTTP 使用 H2/gRPC-like 流量形态,适合 Cloudflare 橙色云直回源
  2. Nginx 负责 TLS、HTTP/2、伪装站和路径分流
  3. Xray 只监听本地回环地址,不直接暴露公网
  4. VPS 443 端口只允许 Cloudflare IP 访问
  5. 客户端启用 ECH、uTLS Chrome、XHTTP padding obfs
  6. 证书使用 acme.sh + Cloudflare DNS-01 自动续期

与 Cloudflare Tunnel 方案相比,本方案性能更直接,XHTTP 兼容性更好;代价是源站必须开放 443 给 Cloudflare,并且需要管理源站证书。


准备工作

所需资源

  1. 一台公网 VPS
  2. 一个已托管到 Cloudflare 的域名
  3. 一个子域名,例如 cdn.example.com
  4. 一套静态网页,用作伪装站

下文假设:

项目示例值
用户名sammy
域名cdn.example.com
Xray 本地端口127.0.0.1:10000
伪装站目录/var/www/camouflage
XHTTP 路径/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
Header Tokenf9e2c4a7b1d8e6c3a0f5b2d9e8c1a4b7d6e0f3a9c2b5d1e8

生成 UUID:

1
cat /proc/sys/kernel/random/uuid

生成随机路径:

1
openssl rand -hex 16

生成 Header Token:

1
openssl rand -hex 24

Cloudflare 设置

DNS

添加 DNS 记录:

类型名称内容代理状态
AcdnVPS IPv4Proxied
AAAAcdnVPS IPv6Proxied,若有 IPv6

必须是橙色云。

SSL/TLS 与 Network

进入 Cloudflare 域名 Dashboard:

位置设置
SSL/TLS → OverviewFull (strict)
SSL/TLS → Edge CertificatesTLS 1.3 On
SSL/TLS → Edge CertificatesMinimum TLS Version: TLS 1.3
SSL/TLS → Edge CertificatesECH Enabled
NetworkHTTP/2 On
NetworkgRPC On

Cloudflare gRPC 要求源站监听 443、支持 TLS、支持 HTTP/2,并通过 ALPN 宣告 HTTP/2。

Cache Rules

为 XHTTP 路径创建缓存绕过规则:

1
2
3
4
5
6
When:
Hostname equals cdn.example.com
AND URI Path starts with /a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

Then:
Cache eligibility: Bypass cache

WAF Skip Rule

创建 WAF Custom Rule:

1
2
3
4
5
Expression:
(http.host eq "cdn.example.com" and starts_with(http.request.uri.path, "/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"))

Action:
Skip

建议跳过:

  • WAF Managed Rules
  • Rate Limiting Rules
  • Super Bot Fight Mode Rules
  • Browser Integrity Check

VPS 初始化

安装基本工具:

1
2
sudo apt update
sudo apt install curl wget vim unzip ca-certificates gnupg lsb-release openssl socat ufw -y

服务器防火墙

只允许 Cloudflare IP 访问 443。本文假设初始化文章已配置并启用 UFW,且 SSH 已放行;本节只新增 Cloudflare 到 443 的规则。

创建脚本目录:

1
sudo mkdir -p /root/scripts/ufw

添加 Cloudflare 规则:

1
sudo vim /root/scripts/ufw/add-cloudflare.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
set -e

mkdir -p /root/scripts/ufw
cd /root/scripts/ufw

curl -sL https://www.cloudflare.com/ips-v4 -o ips-v4
curl -sL https://www.cloudflare.com/ips-v6 -o ips-v6

for ip in $(cat ips-v4); do
ufw allow from "$ip" to any port 443 proto tcp
done

for ip in $(cat ips-v6); do
ufw allow from "$ip" to any port 443 proto tcp
done

更新 Cloudflare 规则:

1
sudo vim /root/scripts/ufw/update-cloudflare.sh
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
#!/bin/bash
set -e

cd /root/scripts/ufw

curl -sL https://www.cloudflare.com/ips-v4 -o ips-v4.new
curl -sL https://www.cloudflare.com/ips-v6 -o ips-v6.new

if [ ! -f ips-v4 ] || [ ! -f ips-v6 ] || ! cmp -s ips-v4 ips-v4.new || ! cmp -s ips-v6 ips-v6.new; then
if [ -f ips-v4 ]; then
for ip in $(cat ips-v4); do
ufw delete allow from "$ip" to any port 443 proto tcp || true
done
fi

if [ -f ips-v6 ]; then
for ip in $(cat ips-v6); do
ufw delete allow from "$ip" to any port 443 proto tcp || true
done
fi

mv ips-v4.new ips-v4
mv ips-v6.new ips-v6

for ip in $(cat ips-v4); do
ufw allow from "$ip" to any port 443 proto tcp
done

for ip in $(cat ips-v6); do
ufw allow from "$ip" to any port 443 proto tcp
done
else
rm -f ips-v4.new ips-v6.new
fi

执行:

1
2
3
sudo chmod +x /root/scripts/ufw/*.sh
sudo bash /root/scripts/ufw/add-cloudflare.sh
sudo ufw status numbered

添加定时更新:

1
sudo crontab -e

加入:

1
2
MAILTO=""
5 3 * * * /bin/bash /root/scripts/ufw/update-cloudflare.sh

安装 Nginx

1
2
3
4
sudo apt update
sudo apt install nginx -y
sudo systemctl enable --now nginx
sudo rm -f /etc/nginx/sites-enabled/default

安装 Xray-core

1
sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

检查版本:

1
xray version

建议使用 v26.3.27 或更新版本。


申请证书

本文使用 acme.sh + Cloudflare DNS-01。这样不需要开放 80,也不受橙色云影响。

acme.sh 可以用普通用户运行,但官方更推荐 root。本文按 root 安装,省去证书目录权限和 sudo reload 的额外配置。

切换到 root

1
sudo -i

后续本节命令均在 root shell 中执行。

安装 acme.sh

1
2
curl https://get.acme.sh | sh -s [email protected]
source ~/.bashrc

如需固定使用 Let’s Encrypt,可执行:

1
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

不执行也可以,acme.sh 会使用自身默认 CA。

准备 Cloudflare API Token

创建 Cloudflare API Token,建议使用以下设置:

  • Permissions:Zone → DNS → Edit
  • Zone Resources:Include → Specific zone → example.com
  • Client IP Address Filtering:Is in,分别添加服务器的 ipv4 和 ipv6 稳定出口地址

设置 token 和 zone ID:

1
2
export CF_Token="你的 Cloudflare API Token"
export CF_Zone_ID="你的 Cloudflare Zone ID"

签发并安装证书

准备证书目录:

1
2
mkdir -p /etc/nginx/ssl
chmod 700 /etc/nginx/ssl

签发 ECC 证书:

1
~/.acme.sh/acme.sh --issue --dns dns_cf -d cdn.example.com --keylength ec-256

安装到 Nginx 使用的路径:

1
2
3
4
~/.acme.sh/acme.sh --install-cert -d cdn.example.com --ecc \
--key-file /etc/nginx/ssl/cdn.example.com.key \
--fullchain-file /etc/nginx/ssl/cdn.example.com.crt \
--reloadcmd "systemctl reload nginx"

检查:

1
ls -l /etc/nginx/ssl/

acme.sh 会在 root 的 crontab 中写入自动续期任务。

退出 root shell:

1
exit

伪装站

创建目录:

1
2
3
sudo mkdir -p /var/www/camouflage
sudo chown -R $USER:$USER /var/www/camouflage
sudo chmod -R 755 /var/www/camouflage

自行准备并放入:

1
2
3
4
/var/www/camouflage/index.html
/var/www/camouflage/404.html
/var/www/camouflage/favicon.ico 可选
/var/www/camouflage/assets/ 可选,CSS/JS/图片等静态资源

推荐使用 Hugo、Hexo、Astro、VitePress 等生成的真实静态站。不要使用 Nginx 默认欢迎页或空白页。


Nginx 配置

编辑:

1
sudo vim /etc/nginx/sites-available/cdn.example.com

写入:

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
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_reject_handshake on;
}

server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;

server_name cdn.example.com;

ssl_certificate /etc/nginx/ssl/cdn.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/cdn.example.com.key;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

root /var/www/camouflage;
index index.html;
error_page 404 /404.html;

location ^~ /a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 {
if ($http_x_access_token != "f9e2c4a7b1d8e6c3a0f5b2d9e8c1a4b7d6e0f3a9c2b5d1e8") {
return 404;
}

grpc_pass grpc://127.0.0.1:10000;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

grpc_connect_timeout 60s;
grpc_send_timeout 3600s;
grpc_read_timeout 3600s;
}

location = /404.html {
internal;
}

location / {
try_files $uri $uri/ =404;
}

access_log /var/log/nginx/cdn.example.com.access.log;
error_log /var/log/nginx/cdn.example.com.error.log warn;
}

第一个 default_server 用于拒绝错误 SNI、直接访问 IP、未带 SNI 的 HTTPS 探测。无需再准备自签默认证书。

启用站点:

1
2
3
sudo ln -sf /etc/nginx/sites-available/cdn.example.com /etc/nginx/sites-enabled/cdn.example.com
sudo nginx -t
sudo systemctl reload nginx

Xray 服务端配置

编辑:

1
sudo vim /usr/local/etc/xray/config.json

写入,注意替换 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
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "vless-xhttp-in",
"listen": "127.0.0.1",
"port": 10000,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "替换为你的 UUID",
"email": "sammy"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"security": "none",
"xhttpSettings": {
"host": "cdn.example.com",
"path": "/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"mode": "auto",
"xPaddingBytes": "100-1000",
"xPaddingObfsMode": true,
"xPaddingMethod": "tokenish",
"xPaddingPlacement": "header",
"xPaddingHeader": "X-Cache",
"xPaddingKey": "_dc",
"serverMaxHeaderBytes": 8192
}
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom"
},
{
"tag": "block",
"protocol": "blackhole"
}
],
"routing": {
"rules": [
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "block"
},
{
"type": "field",
"protocol": [
"bittorrent"
],
"outboundTag": "block"
}
]
}
}

检查并启动:

1
2
3
sudo xray run -test -config /usr/local/etc/xray/config.json
sudo systemctl enable --now xray
sudo systemctl restart xray

检查监听:

1
sudo ss -tlnp | grep 10000

应看到:

1
127.0.0.1:10000

客户端配置

以下为 Xray 客户端示例。注意替换 UUID、域名、路径和 Header Token。

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
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"udp": true
}
},
{
"tag": "http-in",
"listen": "127.0.0.1",
"port": 10809,
"protocol": "http",
"settings": {}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "cdn.example.com",
"port": 443,
"users": [
{
"id": "替换为你的 UUID",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "xhttp",
"security": "tls",
"tlsSettings": {
"serverName": "cdn.example.com",
"alpn": [
"h2"
],
"fingerprint": "chrome",
"echConfigList": "https://1.1.1.1/dns-query",
"echForceQuery": "full"
},
"xhttpSettings": {
"host": "cdn.example.com",
"path": "/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"mode": "auto",
"headers": {
"X-Access-Token": "f9e2c4a7b1d8e6c3a0f5b2d9e8c1a4b7d6e0f3a9c2b5d1e8"
},
"xPaddingBytes": "100-1000",
"xPaddingObfsMode": true,
"xPaddingMethod": "tokenish",
"xPaddingPlacement": "header",
"xPaddingHeader": "X-Cache",
"xPaddingKey": "_dc"
}
}
},
{
"tag": "direct",
"protocol": "freedom"
},
{
"tag": "block",
"protocol": "blackhole"
}
],
"routing": {
"rules": [
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "direct"
},
{
"type": "field",
"protocol": [
"bittorrent"
],
"outboundTag": "block"
}
]
}
}

ECH 策略

隐蔽优先,默认推荐:

1
"echForceQuery": "full"

full 表示拿不到有效 ECHConfig 就不连接,避免回退到明文 SNI。

可用优先,排障时使用:

1
"echForceQuery": "half"

half 表示查询失败时本次不用 ECH,但后续继续尝试。

使用 Cloudflare 优选 IP

如果使用优选 IP,只改:

1
"address": "104.16.132.229"

以下两项保持真实域名:

1
"serverName": "cdn.example.com"
1
"host": "cdn.example.com"

验证

服务状态

1
2
3
sudo systemctl status nginx
sudo systemctl status xray
sudo ss -tlnp | grep -E '443|10000'

应看到:

1
2
3
0.0.0.0:443
[::]:443
127.0.0.1:10000

浏览器访问

访问:

1
https://cdn.example.com

应显示伪装站首页。

访问:

1
https://cdn.example.com/任意不存在路径

应显示自定义 404。

访问隐藏路径:

1
https://cdn.example.com/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

也应显示 404,因为浏览器没有 X-Access-Token

直接访问源站 IP 或错误域名,应在 TLS 握手阶段被拒绝,不应返回站点证书或伪装站内容。

检查 ECHConfig

在客户端本地执行:

1
dig cdn.example.com HTTPS @1.1.1.1

返回中应包含:

1
ech=...

常见问题

Cloudflare 返回 403

检查:

  1. Cloudflare gRPC 是否开启
  2. DNS 是否为橙色云
  3. SSL/TLS 是否为 Full 或 Full strict
  4. Nginx 是否监听 443
  5. Nginx 是否启用 http2 on;

Cloudflare 返回 525 / 526

检查:

  1. 证书是否存在
  2. 证书域名是否匹配 cdn.example.com
  3. 证书是否过期
  4. Cloudflare SSL/TLS 是否为 Full strict

浏览器正常,客户端不通

检查:

  1. UUID 是否一致
  2. 路径是否一致
  3. Header Token 是否一致
  4. 客户端 serverName 是否为真实域名
  5. 客户端 host 是否为真实域名
  6. Xray 是否监听 127.0.0.1:10000
  7. Nginx 是否使用 grpc_pass grpc://127.0.0.1:10000

mode: auto 不稳定

客户端改为:

1
"mode": "packet-up"

服务端保持:

1
"mode": "auto"

ECH 导致连接失败

如果当前是:

1
"echForceQuery": "full"

先改成:

1
"echForceQuery": "half"

如果 half 能连接,说明 ECHConfig 查询或本地 DNS 路径不稳定。

v26 padding 字段报错

升级 Xray-core。若客户端暂不支持,删除以下字段:

1
2
3
4
5
"xPaddingObfsMode": true,
"xPaddingMethod": "tokenish",
"xPaddingPlacement": "header",
"xPaddingHeader": "X-Cache",
"xPaddingKey": "_dc"

只保留:

1
"xPaddingBytes": "100-1000"

客户端和服务端要保持一致。


更新维护

更新系统组件

1
2
3
sudo apt update
sudo apt upgrade -y
sudo systemctl reload nginx

更新 Xray

1
2
3
sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
sudo systemctl restart xray
xray version

手动续期测试

1
sudo /root/.acme.sh/acme.sh --cron --home /root/.acme.sh --force

轮换 UUID、路径和 Token

建议 3 到 6 个月轮换一次:

1
2
3
xray uuid
openssl rand -hex 16
openssl rand -hex 24

同步修改:

  1. Xray 服务端 UUID
  2. Xray 服务端 path
  3. Nginx location path
  4. Nginx Header Token
  5. 客户端 UUID
  6. 客户端 path
  7. 客户端 Header Token
  8. Cloudflare Cache Rule
  9. Cloudflare WAF Skip Rule

最终配置摘要

项目推荐值
架构Cloudflare 橙色云 → Nginx → Xray
DNSProxied
Cloudflare SSL/TLSFull strict
Cloudflare gRPCOn
源站端口443
源站证书acme.sh DNS-01 自动续期
Nginx HTTP/2http2 on;
未知 SNI / 直接 IPssl_reject_handshake on;
Nginx 到 Xraygrpc_pass grpc://127.0.0.1:10000
Xray 监听127.0.0.1:10000
Xray 入站VLESS + XHTTP
服务端 TLSNginx 处理,Xray security: none
客户端 TLSsecurity: tls
客户端 ALPNh2
客户端 fingerprintchrome
XHTTP modeauto
fallback modepacket-up
ECH 默认full
ECH 排障half
paddingxPaddingBytes + v26 obfs tokenish
伪装站目录/var/www/camouflage

References

Xray-core XHTTP #4113

Xray-install

Xray Transport / ECH

Cloudflare gRPC connections

Cloudflare Full strict

Cloudflare ECH

Cloudflare IP ranges

Cloudflare Cache Rules

Cloudflare WAF Skip action

Nginx changelog

Nginx grpc module

Nginx SSL module

acme.sh

acme.sh Cloudflare DNS API

Mastodon