本文介绍如何用 Caddy 在服务器公网 IP:443 上终结 TLS,并把 WebSocket 流量 reverse proxy 到本机 Xray,同时满足:
- 使用 self-signed certificate(不依赖域名、不走公网 ACME)
- 客户端 SNI 可任意填写(便于应对部分 SNI blocking)
- 客户端 WebSocket Host 可任意填写(不必等于 IP 或 SNI)
- 本机其它真实域名站点可与 Xray 按 SNI 分流共存
文中 IP、UUID、域名均为 placeholder,发表与部署时请替换为你自己的值。
1. 架构说明
1 | Client |
各层职责如下:
| Layer | 由谁处理 | 作用 |
|---|---|---|
| TCP destination | 客户端 address 填公网 IP | 流量真正打到你的机器 |
| TLS SNI | Caddy | 选证书、完成 handshake |
| HTTP Host / WS Host | 原样透传 | Xray 不强制匹配 Host |
| path | Caddy + Xray | 固定入口路径,例如 /rayws |
地址填 IP、SNI 填其它 hostname 是合法用法:SNI 只是 TLS ClientHello 里的字段,并不要求该名字 DNS resolve 到你的 IP。部分网络只 string-match 特定 SNI,换成常见 hostname 有时可以绕过。
2. 前置条件
- Linux server,已开放 443(80 可选)
- 已安装 Caddy、Xray
- 建议 Xray 只 listen
127.0.0.1,不要直接暴露到公网 - 客户端需支持 allowInsecure / skip certificate verification(self-signed 场景必须)
下文使用的 placeholder:
1 | # Replace with your own values |
203.0.113.0/24 是文档用 reserved address(RFC 5737),请换成你的 public IP。
3. 安装组件(简述)
3.1 Caddy(Debian / Ubuntu 示例)
1 | apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gnupg |
3.2 Xray
1 | bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install |
生成 UUID:
1 | cat /proc/sys/kernel/random/uuid |
4. Xray 配置(关键:不校验 Host)
写入 /usr/local/etc/xray/config.json:
1 | { |
不要在 wsSettings.headers 里写死 Host。
一旦配置了类似 "Host": "example.com",server 往往会要求 client Host 必须匹配,就无法做到 “arbitrary Host”。
TLS 由 Caddy 终结;Xray inbound 保持 "security": "none" 即可(本机 plain WebSocket,外网对 Caddy 仍是 HTTPS)。
权限与 service(若以 nobody 运行):
1 | mkdir -p /var/log/xray |
5. Caddy 配置(核心)
编辑 /etc/caddy/Caddyfile:
1 | { |
5.1 指令说明
| Directive | 作用 |
|---|---|
default_sni |
client 未带 SNI 时的 fallback hostname |
:443 |
监听所有 hostname(catch-all),不绑死单一 domain |
tls internal |
使用 Caddy local CA,不走 public ACME |
on_demand |
按 client 当前 SNI 即时签发 certificate |
@xray path /rayws* |
仅该 path 转发到 Xray |
reverse_proxy 127.0.0.1:10000 |
反代到本机 Xray;默认不改写 Host |
| Named site block | 可选真实站点;按 SNI 优先于 catch-all |
5.2 校验并 reload
1 | caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile |
5.3 可选:导出 local root CA
1 | ls /var/lib/caddy/.local/share/caddy/pki/authorities/local/ |
6. Firewall
1 | ufw allow 22/tcp |
7. 服务端自测
7.1 默认访问
1 | curl -sk "https://203.0.113.10/" |
7.2 任意 SNI
1 | curl -sk --connect-to "www.example-cdn.com:443:203.0.113.10:443" \ |
返回 OK 说明 Caddy 已接受该 SNI 并完成自签名握手。完整 VLESS 连通还需要用 client 测 /rayws。
7.3 path 是否打到 Xray
1 | curl -sk -o /dev/null -w "%{http_code}\n" \ |
非 404(例如 400)说明请求已进入 Xray。
8. 客户端配置
8.1 固定项与可变项
| Field | 是否固定 | 取值说明 |
|---|---|---|
| Address | 固定 | 服务器 public IP |
| Port | 固定 | 443 |
| UUID | 固定 | 与 server 一致 |
| Protocol | 固定 | VLESS |
| Transport | 固定 | WebSocket |
| path | 固定 | /rayws |
| TLS | 固定 | 开启 |
| allowInsecure / skip verify | 固定 | 必须开启(self-signed) |
| SNI | 可变 | 任意 hostname,可随时改 |
| WS Host | 可变 | 任意值,可与 SNI 不同 |
8.2 Share link 示例
1 | # SNI and Host use the same popular hostname |
8.3 图形客户端勾选清单
- Address = 服务器 public IP
- Port =
443 - TLS = on
- Allow insecure / skip certificate verification = on
- SNI = 你想伪装的 hostname(被拦时可改)
- Transport =
ws,path =/rayws - Host(若有单独一栏)= 任意(可随时改)
链路被过滤时,优先 只改 SNI;仍不通再改 Host;不要轻易改 path / UUID / port。
9. 同机有真实域名时如何共存
Caddy 对 更具体的 host matcher 优先:
1 | app.example.com { |
- Client SNI =
app.example.com→ 真实站点 + public certificate - Client SNI = 其它任意值 → internal CA + Xray catch-all
因此不要把本机 production hostname 当作 Xray 的伪装 SNI,否则流量会进 named site 而非 catch-all。
10. 常见问题
Q1:必须开启 allowInsecure 吗?
是。 tls internal 签发的证书不被系统/浏览器默认信任。要么开启 allowInsecure,要么在支持自定义 CA 的 client 中导入 Caddy local root。
Q2:”任意 SNI” 在网络路径上一定有效吗?
Server 侧:可以。 Network 侧:不保证。 仅做 SNI string-match 的中间设备,换 SNI 可能绕过;若还校验 IP 归属,仍可能被拦。
Q3:为什么 path 要固定?
避免整站 reverse proxy 过于开放。path 过宽更容易被探测。
Q4:在 Xray 里写了 Host 后,client 必须一致吗?
会。 若要 arbitrary Host,删除 wsSettings.headers.Host,也不要在 Caddy 里用 header_up Host 写死常量。
Q5:on_demand 安全吗?
本文仅使用 tls internal(local CA),不消耗 public ACME。建议 Xray 仅 listen localhost、firewall 只放行 22/80/443、UUID 足够随机。
Q6:与域名证书、REALITY 等方案如何对比?
| Approach | 优点 | 缺点 |
|---|---|---|
| IP + self-signed + flexible SNI(本文) | 不绑 domain;SNI / Host 灵活 | 必须 skip cert verify |
| Domain + public cert + fixed SNI | 证书可信 | SNI 被拦不好换 |
| REALITY | 伪装更强 | 配置复杂 |
11. 最小可运行清单
Xray: 127.0.0.1:10000,VLESS + WS,path = /rayws,不校验 Host,inbound 无 TLS。
Caddy:
1 | { |
Client: address = IP,port = 443,path = /rayws,TLS + allowInsecure;SNI 与 WS Host 可随时修改。