前言
由于工作环境的需要,我们通常会用到代理服务。过去我使用过 x-ui 来搭建面板,但是原项目维护不算活跃,后来出现了不少变种版本。这里我使用的是 3x-ui,并记录一次完整的 Docker 部署、Nginx 反向代理、SSL 证书、Cloudflare 小黄云、订阅链接优化以及 Reality 节点配置经验。
本文目标:
1. 使用 Docker 部署 3x-ui,尽量不影响服务器其他服务
2. 使用 Nginx 反代 3x-ui 面板、订阅服务和 WebSocket 节点
3. 通过 Cloudflare 小黄云代理 VLESS + WS + TLS 节点
4. 让 3x-ui 面板生成的订阅链接可以直接给用户使用
5. Reality 节点单独使用灰云域名直连,不经过 Cloudflare 小黄云
本文使用的示例信息如下,实际部署时请替换成自己的:
面板域名 / CDN 域名:cdn.example.com
Reality 域名:reality.example.com
3x-ui 面板端口:2096
3x-ui 订阅端口:8443
VLESS + WS 入站端口:2083
VLESS + Reality 入站端口:32069
WebSocket 路径:/american
订阅路径:/v2ray-sub/
准备工作
需要准备:
1. VPS 一台
2. 域名一个
3. 域名已经解析到 VPS
4. 服务器系统建议使用 Debian / Ubuntu
5. 已经开放必要端口,例如 80、443、Reality 入站端口等
示例 DNS 规划:
cdn.example.com 开启 Cloudflare 小黄云
reality.example.com 关闭 Cloudflare 小黄云,DNS only
其中:
cdn.example.com
用于:
3x-ui 面板访问
3x-ui 订阅链接
VLESS + WS + TLS 节点
而:
reality.example.com
用于:
VLESS + TCP + Reality 节点
Reality 节点不建议走 Cloudflare 小黄云,应该使用灰云域名直连 Xray 入站端口。
安装 Docker 和 Docker Compose
下面以 Debian / Ubuntu 为例。
1. 更新系统
sudo apt update
sudo apt upgrade -y
2. 安装依赖
sudo apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release
3. 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
如果你使用的是 Ubuntu,把上面的 Docker 源地址换成 Ubuntu 对应地址。
4. 添加 Docker 软件源
Debian 示例:
echo \
"deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
更新软件源:
sudo apt update
5. 安装 Docker
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
查看 Docker 状态:
systemctl status docker
使用 Docker 部署 3x-ui
创建目录:
mkdir -p ~/x-ui
cd ~/x-ui
创建 docker-compose.yml:
vim docker-compose.yml
写入以下内容:
services:
xui:
image: ghcr.io/mhsanaei/3x-ui:latest
container_name: xui
volumes:
- ./db/:/etc/x-ui/
- ./cert/:/root/cert/
environment:
- XRAY_VMESS_AEAD_FORCED=false
restart: unless-stopped
network_mode: host
cap_drop:
- ALL
cap_add:
- NET_ADMIN
启动容器:
docker compose up -d
查看容器:
docker ps
查看日志:
docker logs -f xui
进入 3x-ui 管理脚本:
docker exec -it xui x-ui
常用命令:
# 停止并删除容器
docker compose down
# 更新镜像
docker pull ghcr.io/mhsanaei/3x-ui:latest
# 重启容器
docker restart xui
端口规划
建议先规划清楚端口,避免后面冲突。
本文示例端口如下:
Nginx 对外 HTTP:80
Nginx 对外 HTTPS:443
3x-ui 面板端口:2096
3x-ui 订阅端口:8443
VLESS + WS 入站端口:2083
VLESS + Reality 入站端口:32069
重点注意:
80 和 443 给 Nginx 使用
3x-ui 面板端口不要设置成 80 或 443
3x-ui 订阅端口不要设置成 443
Xray 入站端口不要和 Nginx、面板、订阅端口冲突
如果把 3x-ui 的面板端口或订阅端口改成 443,而 443 已经被 Nginx 使用,3x-ui 就会启动失败。
可以查看端口占用:
ss -lntp | grep -E ':80|:443|:2096|:8443|:2083|:32069'
安装 Nginx 和 Certbot
安装 Nginx:
sudo apt update
sudo apt install -y nginx
安装 Certbot:
sudo apt install -y snapd
sudo systemctl restart snapd
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
创建 Nginx 配置文件:
touch /etc/nginx/conf.d/xui.conf
先写一个最简单的 HTTP 配置用于申请证书:
server {
listen 80;
listen [::]:80;
server_name cdn.example.com;
location / {
proxy_pass http://127.0.0.1:2096;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
检查配置:
nginx -t
申请证书:
certbot --nginx --agree-tos --no-eff-email --email [email protected] -d cdn.example.com
证书申请成功后,Certbot 通常会自动修改 Nginx 配置。后面我们会给出完整的最终配置。
测试续期:
certbot renew --dry-run
查看证书:
certbot certificates
删除证书:
certbot delete --cert-name cdn.example.com
Cloudflare 小黄云配置说明
Cloudflare 小黄云适合代理:
HTTP
HTTPS
WebSocket
所以适合:
VLESS + WS + TLS
VMess + WS + TLS
Trojan + WS + TLS
不适合直接代理:
VLESS + TCP + Reality
VLESS + TCP
普通 TCP 转发
UDP 流量
因此推荐:
cdn.example.com 小黄云,给 WS + TLS 使用
reality.example.com 灰云,给 Reality 直连使用
3x-ui 面板配置
面板端口
3x-ui 面板端口建议使用:
2096
不要设置为:
80
443
因为 80 和 443 应该交给 Nginx 使用。
外部用户访问:
https://cdn.example.com
Nginx 内部转发到:
https://127.0.0.1:2096
3x-ui 订阅服务配置
为了让 3x-ui 面板生成的订阅链接可以直接给用户使用,推荐这样配置订阅服务。
在 3x-ui 面板中找到订阅设置,示例配置如下:
监听域名:留空
监听端口:8443
URI 路径:/v2ray-sub/
反向代理 URI:https://cdn.example.com/v2ray-sub/
说明:
监听域名留空:
表示监听所有地址,方便 Nginx 通过 127.0.0.1 访问。
监听端口 8443:
这是 3x-ui 订阅服务真实监听端口,不要设置成 443。
URI 路径 /v2ray-sub/:
这是订阅服务内部路径,必须以 / 开头并以 / 结尾。
反向代理 URI:
这是对外展示给用户使用的订阅地址前缀。
配置完成后,3x-ui 面板生成的订阅链接应该类似:
https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx
而不是:
https://cdn.example.com:8443/v2ray-sub/xxxxxxxxxxxx
如果反向代理 URI 只填写:
/v2ray-sub/
有些版本的 3x-ui 可能只显示相对路径:
/v2ray-sub/xxxxxxxxxxxx
如果希望用户复制出来就是完整链接,建议填写完整公网前缀:
https://cdn.example.com/v2ray-sub/
VLESS + WS + TLS 入站配置
Cloudflare 小黄云节点建议使用:
VLESS + WebSocket + TLS
但是这里要区分:
Xray 真实监听配置
客户端对外连接配置
Xray 真实入站可以配置为:
协议:VLESS
监听端口:2083
传输:ws
路径:/american
TLS:关闭
Reality:关闭
Flow:留空
为什么 Xray 入站 TLS 关闭?
因为 TLS 由 Nginx 在 443 上处理,Nginx 再把请求以普通 WebSocket 的方式转发到:
http://127.0.0.1:2083
客户端对外连接应该是:
地址:cdn.example.com
端口:443
TLS:开启
SNI:cdn.example.com
Host:cdn.example.com
传输:ws
路径:/american
也就是说:
客户端连接 cdn.example.com:443
Nginx 转发到 127.0.0.1:2083
不要让客户端直接使用:
cdn.example.com:2083
除非你不走 Cloudflare 小黄云。
让订阅内容里的节点也使用 443 + TLS
订阅链接本身正确,并不代表订阅内容里的节点参数一定正确。
例如订阅链接可能已经是:
https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx
但是订阅内容里的节点可能还是:
vless://[email protected]:2083?security=none&type=ws&path=%2Famerican
这对 Cloudflare 小黄云场景是不合适的。
CDN 节点应该生成类似:
vless://[email protected]:443?security=tls&type=ws&host=cdn.example.com&sni=cdn.example.com&path=%2Famerican
因此在 3x-ui 的入站配置中,如果你的版本提供类似以下选项:
外部代理
External Proxy
外部端口
分享链接配置
订阅节点外部参数
建议将 CDN 入站的对外参数设置为:
地址:cdn.example.com
端口:443
TLS:开启
SNI:cdn.example.com
Host:cdn.example.com
Path:/american
注意:
真实监听端口仍然是 2083
对外分享端口是 443
如果你的 3x-ui 版本没有类似的外部代理设置,那么订阅链接可以反代成功,但订阅内容可能仍会按真实入站端口生成。这种情况下需要手动调整节点、升级 3x-ui,或者使用订阅转换工具处理端口和 TLS 参数。
Nginx 完整配置示例
下面是一份完整 Nginx 配置。
请将:
cdn.example.com
替换成自己的域名。
请将证书路径替换成自己 Certbot 实际生成的路径。
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name cdn.example.com;
ssl_certificate /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# VLESS + WS 节点入口
# 客户端访问:wss://cdn.example.com/american
# Nginx 转发到:127.0.0.1:2083
location = /american {
proxy_pass http://127.0.0.1:2083;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# 3x-ui 订阅反代入口
# 3x-ui 订阅服务真实监听端口:8443
# 用户实际订阅地址:https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx
location /v2ray-sub/ {
proxy_pass https://127.0.0.1:8443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_redirect off;
proxy_ssl_verify off;
}
# 3x-ui 面板入口
# 3x-ui 面板真实监听端口:2096
location / {
proxy_pass https://127.0.0.1:2096;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_ssl_verify off;
}
}
server {
listen 80;
listen [::]:80;
server_name cdn.example.com;
return 301 https://$host$request_uri;
}
保存后检查配置:
nginx -t
没有报错后重载 Nginx:
systemctl reload nginx
测试 WebSocket 是否正常
可以使用下面的命令测试 WebSocket 握手:
curl --http1.1 -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jQ==" \
-H "Sec-WebSocket-Version: 13" \
https://cdn.example.com/american
如果配置正常,会返回类似:
HTTP/1.1 101 Switching Protocols
这说明:
Cloudflare -> Nginx -> Xray WS 入站
链路已经打通。
如果直接使用普通 curl 访问 WebSocket 地址,可能会返回:
400 Bad Request
这不一定是错误,因为 WebSocket 需要 Upgrade 握手头。
测试订阅链接
订阅链接应该类似:
https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx
可以用 curl 测试:
curl -k https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx
如果返回的是 base64 内容,可以解码查看:
curl -k https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx | base64 -d
对于 Cloudflare CDN 节点,订阅内容里应该是:
地址:cdn.example.com
端口:443
security:tls
type:ws
path:/american
host:cdn.example.com
sni:cdn.example.com
如果看到:
端口:2083
security:none
说明订阅 URL 本身已经反代成功,但是节点对外参数还没有设置好。此时需要回到 3x-ui 入站配置里设置外部代理或分享参数。
Reality 节点配置
Reality 节点不建议走 Cloudflare 小黄云。
建议单独使用一个灰云域名,例如:
reality.example.com
Cloudflare DNS 添加:
类型:A
名称:reality
内容:VPS 公网 IP
代理状态:DNS only
也就是关闭小黄云。
Reality 入站示例:
协议:VLESS
传输:TCP
安全:Reality
监听端口:32069
SNI:按自己的 Reality 配置填写
Flow:按自己的客户端配置保持一致
客户端连接:
reality.example.com:32069
Reality 不需要 Nginx 反代,也不要写 Nginx 的 location。它应该由客户端直接连接到 Xray 的 Reality 入站端口。
如果使用 UFW,需要放行端口:
ufw allow 32069/tcp
检查端口监听:
ss -lntp | grep 32069
开启 BBR 加速
创建配置文件:
sudo nano /etc/sysctl.d/99-bbr.conf
写入:
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
保存后执行:
sudo sysctl --system
或者:
sudo sysctl -p /etc/sysctl.d/99-bbr.conf
查看是否生效:
sysctl net.ipv4.tcp_congestion_control
如果输出:
net.ipv4.tcp_congestion_control = bbr
说明已经开启。
Certbot 常用命令
查看证书:
certbot certificates
手动测试续期:
certbot renew --dry-run
为指定域名申请证书:
certbot --nginx -d example.com -d www.example.com
删除证书:
certbot delete --cert-name example.com
常见问题排查
1. 3x-ui 面板或订阅服务无法启动
优先检查端口冲突:
ss -lntp | grep -E ':80|:443|:2096|:8443|:2083|:32069'
如果 3x-ui 订阅端口被设置成了 443,而 Nginx 已经占用了 443,3x-ui 会启动失败。
如果面板已经无法打开,可以直接修改数据库。
进入 3x-ui 目录:
cd ~/x-ui
查看数据库表:
sqlite3 ./db/x-ui.db ".tables"
查看端口相关设置:
sqlite3 ./db/x-ui.db "select id, key, value from settings where lower(key) like '%port%' or lower(key) like '%sub%' or value like '%443%';"
如果看到:
subPort|443
可以改回:
sqlite3 ./db/x-ui.db "update settings set value='8443' where key='subPort';"
然后重启容器:
docker restart xui
2. WebSocket 返回 101,但客户端无法代理
如果测试返回:
HTTP/1.1 101 Switching Protocols
说明 Nginx 和 Xray 的 WebSocket 链路是通的。
此时重点检查客户端节点参数:
端口是否是 443
TLS 是否开启
SNI 是否是 cdn.example.com
Host 是否是 cdn.example.com
Path 是否是 /american
UUID 是否一致
Flow 是否留空
如果客户端只把端口从 2083 改成 443,但是 TLS 仍然关闭,也无法正常使用。
正确的 CDN 节点应该是:
端口:443
security:tls
type:ws
path:/american
host:cdn.example.com
sni:cdn.example.com
3. 订阅链接正确,但节点不可用
订阅链接正确只代表客户端能获取订阅内容,不代表节点参数一定正确。
可以解码订阅内容:
curl -k https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx | base64 -d
如果 CDN 节点仍然是:
cdn.example.com:2083
security=none
那客户端会按普通 WS 访问 2083,通常无法通过 Cloudflare 小黄云正常使用。
应当让订阅内容生成:
cdn.example.com:443
security=tls
4. Reality 节点无法连接
Reality 节点应检查:
域名是否是灰云 DNS only
端口是否放行
Xray 是否监听对应端口
客户端 UUID、Reality 公钥、shortId、SNI 是否一致
检查监听:
ss -lntp | grep 32069
Reality 不走 Nginx,也不走 Cloudflare 小黄云。
最终推荐架构
最终推荐结构如下:
VLESS + WS + TLS:
客户端 -> Cloudflare 小黄云 -> Nginx 443 -> Xray WS 入站 2083
3x-ui 面板:
浏览器 -> Cloudflare 小黄云 -> Nginx 443 -> 3x-ui 面板 2096
3x-ui 订阅:
客户端 -> Cloudflare 小黄云 -> Nginx 443 -> 3x-ui 订阅服务 8443
VLESS + TCP + Reality:
客户端 -> 灰云域名 -> Xray Reality 入站 32069
这种方式可以避免端口冲突,也方便让用户直接使用 3x-ui 面板生成的订阅链接:
https://cdn.example.com/v2ray-sub/xxxxxxxxxxxx
同时,Reality 节点也不会被 Cloudflare 小黄云影响。
评论区