传输层与 TLS
传输层负责在两台机器之间承载代理协议。在 VLESS、VMess、Trojan 或 HTTP 连接下面,以及 SOCKS 或 Shadowsocks 出站下面,etemenanki-app 可以使用明文 TCP、TLS、WebSocket,或者基于 HTTP/2 的 gRPC 隧道,还可以在 WebSocket 或 gRPC 下面再加一层 TLS。服务端用 [inbound.stream] 表选择传输层,客户端用 [outbound.stream] 表选择。一条连接的两端必须选择相同的传输层。
本页介绍 stream 表及其 tls、ws、grpc 子表的每个字段:哪些组合合法、哪些协议能使用传输层、证书如何加载和校验,以及各承载方式固定的超时和上限。当你要把代理放在 TLS、CDN 或反向代理后面,或者要连接这样部署的 Xray 服务器时,请阅读本页。katana 使用相同的传输层,但设置来自面板,详见 katana 自己的页面。
各层如何叠加
Section titled “各层如何叠加”每种传输层都从一条 TCP 连接开始。network 选择其上的承载方式,security = "tls" 则在 TCP 与 WebSocket 或 gRPC 承载之间加入 TLS。代理协议运行在最内层。
flowchart LR tcp["TCP 连接"] tlsA["TLS,无 ALPN"] tlsB["TLS,ALPN 为 http/1.1 或 h2"] carrier["WebSocket,或基于 HTTP/2 的 gRPC"] proto["代理协议:VLESS、VMess、Trojan……"] tcp -->|"network = tcp"| proto tcp -->|"network = tls"| tlsA --> proto tcp -->|"ws 或 grpc,security = tls"| tlsB --> carrier tcp -->|"ws 或 grpc,无 security"| carrier carrier --> proto
WebSocket 每条 TCP 连接只承载一条代理连接。gRPC 入站在每条 TCP 连接上接受多条隧道,每个 HTTP/2 stream 一条;gRPC 出站则为它拨号的每个流新建一条 HTTP/2 连接。
一台在 443 端口上监听、使用 WebSocket 加 TLS 的 VLESS 服务端,以及连接它的客户端。服务端需要一张 proxy.example.com 的证书;客户端用系统根证书校验它。
[[inbound]]tag = "vless-in"protocol = "vless"listen = "0.0.0.0"port = 443
[inbound.stream]network = "ws"security = "tls"
[inbound.stream.ws]path = "/ray"
[inbound.stream.tls]cert_file = "/etc/etemenanki/cert.pem"key_file = "/etc/etemenanki/key.pem"
[inbound.settings]users = [{ id = "11111111-2222-3333-4444-555555555555" }]
[[outbound]]tag = "direct"protocol = "freedom"[[inbound]]tag = "socks-in"protocol = "socks"port = 1080
[[outbound]]tag = "proxy"protocol = "vless"server = "proxy.example.com"port = 443
[outbound.stream]network = "ws"security = "tls"
[outbound.stream.ws]path = "/ray?ed=2048" # 服务端仍然匹配 /ray;?ed= 用于开启 early data
[outbound.settings]id = "11111111-2222-3333-4444-555555555555"客户端既没有设置 tls.server_name,也没有设置 ws.host,所以两者都回退到 server:它发送 SNI proxy.example.com,按这个名称校验证书,并发送 Host: proxy.example.com。VLESS over WebSocket 与 TLS 演示了完整的部署过程,VMess over gRPC 与 TLS 则是 gRPC 版本。
每次修改后都运行 etemenanki-app --test -c server.toml。--test 会校验 stream 表,并读取证书、私钥和 CA 文件,所以本页中除了运行中连接才会出现的错误之外,其他错误都能在这一步发现。
stream 表
Section titled “stream 表”[inbound.stream] 和 [outbound.stream] 使用同一套字段。省略这个表等同于 network = "tcp"。任何层级(包括子表)出现未知字段都会报错。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
network | string (enum) | 否 | "tcp" | 协议下面的承载方式:tcp(明文 TCP)、tls(TLS over TCP)、ws(WebSocket)或 grpc(基于 HTTP/2 的 gRPC)。区分大小写,也不会去除首尾空格:"WS"、" ws" 和 "" 都会报 unknown stream network。没有传输层的协议在这里只接受 tcp(或空字符串)。 |
security | string (enum) | 否 | "none" | none(或空字符串)表示不再加一层,tls 表示在 ws 或 grpc 下面加一层 TLS。首尾空格会被忽略,但区分大小写。其他任何值都报 unknown stream security "..." (expected "tls" or "none")。network = "tcp" 时不接受 tls,应改写为 network = "tls"。network = "tls" 时,无论这里写什么,连接都是 TLS。 |
tls | table | 视情况 | — | 证书、私钥和证书校验设置,只有 stream 真正用到 TLS 时才会读取;例外是 ws 或 grpc 出站总会读取 server_name,作为 Host 或 :authority 的后备值。使用 TLS 的入站必须在这里写 cert_file 和 key_file。见 [stream.tls] 表。 |
ws | table | 否 | — | WebSocket 的路径和 Host,只有 network = "ws" 时才会读取。见 [stream.ws] 表。 |
grpc | table | 视情况 | — | gRPC 的服务名和 authority,只有 network = "grpc" 时才会读取;此时必须设置 grpc.service_name。见 [stream.grpc] 表。 |
ws 和 grpc 子表只在对应的 network 下才会读取,tls 中的证书和校验字段也只在 stream 使用 TLS 时才会读取。在 network = "grpc" 下写一个 [inbound.stream.ws] 表会被接受,但不起作用;在明文 ws network 下的 [outbound.stream.tls] 里写 ca_file 或 allow_insecure 也一样,即使文件不存在也不报错。例外是 tls.server_name:ws 或 grpc 出站无论是否使用 TLS,都会把它作为 ws.host 或 grpc.authority 的后备值。
network 与 security 的组合
Section titled “network 与 security 的组合”network 指定承载方式,security 要求在其下面加一层 TLS。etemenanki-app 会校验这两个值的组合,而不是只把 security 与 "tls" 比较、其余情况一律走明文分支:如果配置要求 TLS 却悄悄得到明文,代理凭据会在任何错误出现之前就以明文发出。
network |
security 缺省、"" 或 "none" |
security = "tls" |
其他 security 值 |
|---|---|---|---|
"tcp"(默认) |
明文 TCP | 拒绝:请使用 network = "tls" |
拒绝 |
"tls" |
TLS over TCP | TLS over TCP | 拒绝 |
"ws" |
基于明文 TCP 的 WebSocket | 基于 TLS 的 WebSocket | 拒绝 |
"grpc" |
基于明文 HTTP/2(h2c)的 gRPC | 基于 HTTP/2 over TLS 的 gRPC | 拒绝 |
| 其他任何值 | 拒绝 | 拒绝 | 拒绝 |
network = "tls" 是本项目自己对 TLS over TCP 的写法。Xray 把同样的东西写成 "network": "tcp" 加 "security": "tls",而这恰好是 etemenanki-app 唯一拒绝的组合,错误信息会直接给出改法:
configuration invalid: inbound vless-in: security = "tls" is not valid with network = "tcp"; use network = "tls" for TLS over plain TCP (security = "tls" layers TLS under network = "ws" or "grpc")其他错误如下:
inbound vless-in: unknown stream network "WS"inbound vless-in: unknown stream security "reality" (expected "tls" or "none")哪些协议可以使用传输层
Section titled “哪些协议可以使用传输层”自己掌管连接、或者从不拨号代理服务器的协议,无法承载传输层。etemenanki-app 不会悄悄丢弃这类协议的 stream 表,免得你以为端口已经伪装、实际却是裸露的,而是直接拒绝。
| 方向 | 接受 [stream] |
拒绝传输层 |
|---|---|---|
| 入站 | IP 监听器上的 http、trojan、vless、vmess |
socks、shadowsocks、hysteria2、tun,以及 Unix socket 上的所有协议 |
| 出站 | socks、http、trojan、vless、vmess、shadowsocks |
freedom、blackhole、hysteria2、wireguard |
对拒绝传输层的协议,network 只能缺省、为 "" 或 "tcp",security 只能缺省、为 "" 或 "none";这里会忽略首尾空格。其他值都会失败:
inbound socks-in: protocol socks does not support stream network "ws"inbound ss: protocol shadowsocks does not support stream security "tls"inbound local: protocol vless over a unix socket does not support stream network "ws"outbound direct: protocol freedom does not support stream network "ws"Hysteria 2 总是运行在自带 TLS 的 QUIC 之上。它的证书、私钥和校验字段位于 [inbound.settings] 和 [outbound.settings],见 Hysteria 2。
每个标签页给出服务端配置和对应的客户端配置,省略了协议本身的 [inbound.settings] 和 [outbound.settings]。
不需要 stream 表;下面两段与省略它效果相同。
[inbound.stream]network = "tcp"[outbound.stream]network = "tcp"[inbound.stream]network = "tls"
[inbound.stream.tls]cert_file = "/etc/etemenanki/cert.pem"key_file = "/etc/etemenanki/key.pem"[outbound.stream]network = "tls"# 未设置 tls.server_name 时,SNI 和证书校验使用 `server`[inbound.stream]network = "ws"
[inbound.stream.ws]path = "/ray"[outbound.stream]network = "ws"
[outbound.stream.ws]path = "/ray"[inbound.stream]network = "ws"security = "tls"
[inbound.stream.ws]path = "/ray"host = "proxy.example.com" # 可选:拒绝其他 Host 头
[inbound.stream.tls]cert_file = "/etc/etemenanki/cert.pem"key_file = "/etc/etemenanki/key.pem"[outbound.stream]network = "ws"security = "tls"
[outbound.stream.ws]path = "/ray?ed=2048"[inbound.stream]network = "grpc"
[inbound.stream.grpc]service_name = "tunnel"[outbound.stream]network = "grpc"
[outbound.stream.grpc]service_name = "tunnel"[inbound.stream]network = "grpc"security = "tls"
[inbound.stream.grpc]service_name = "tunnel"
[inbound.stream.tls]cert_file = "/etc/etemenanki/cert.pem"key_file = "/etc/etemenanki/key.pem"[outbound.stream]network = "grpc"security = "tls"
[outbound.stream.grpc]service_name = "tunnel"[inbound.stream.tls] 和 [outbound.stream.tls] 存放所有 TLS 形态的证书设置:network = "tls",以及带 security = "tls" 的 ws 或 grpc。这个表只有一套字段,但入站只读取 cert_file 和 key_file,出站只读取另外三个。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
server_name | string | 否 | — | 仅用于出站。作为 SNI 发送、并用来校验服务器证书的名称。未设置时使用出站的 server。如果这个名称是 IP 地址,则不发送 SNI,证书里必须包含该 IP。ws 或 grpc 出站(无论是否带 TLS)也会拿它作为 ws.host 或 grpc.authority 的后备值。入站忽略此项。 |
allow_insecure | bool | 否 | false | 仅用于出站。完全跳过证书链和主机名校验。不能与 ca_file 同时设置(报 tls.allow_insecure and tls.ca_file cannot both be set)。入站忽略此项。 |
ca_file | path | 否 | — | 仅用于出站。PEM 格式的 CA 证书文件,在系统根证书之外额外信任这些 CA;主机名仍然会校验。文件里没有证书时报 no certificate in CA PEM bundle。不能与 allow_insecure 同时设置。入站忽略此项。 |
cert_file | path | 视情况 | — | 仅用于入站;入站使用 TLS 时必填(否则报 tls stream needs tls.cert_file)。PEM 格式的证书链:叶子证书在前,中间证书在后。出站忽略此项,不支持客户端证书。 |
key_file | path | 视情况 | — | 仅用于入站;入站使用 TLS 时必填(否则报 tls stream needs tls.key_file)。与叶子证书对应的 PEM 私钥;私钥与证书不匹配时报 no private key assigned。出站忽略此项。 |
入站:证书与私钥
Section titled “入站:证书与私钥”使用 TLS 的入站需要这两个文件。证书文件是 PEM 格式的证书链,叶子证书在前,后面跟着所有中间证书,也就是大多数 ACME 客户端签发的 fullchain.pem。私钥必须与叶子证书匹配。
[inbound.stream.tls]cert_file = "/etc/etemenanki/fullchain.pem"key_file = "/etc/etemenanki/privkey.pem"无论客户端发送什么 SNI,入站都向所有客户端提供这同一张证书;它不按名称选择证书,也不拒绝未知的名称。它从不要求客户端出示证书。
| 问题 | 错误 |
|---|---|
缺少 cert_file |
inbound vless-in: tls stream needs tls.cert_file |
缺少 key_file |
inbound vless-in: tls stream needs tls.key_file |
| 文件不存在或无法读取 | No such file or directory (os error 2),或其他操作系统错误 |
cert_file 中没有 PEM 证书(例如私钥和证书的路径写反了) |
no certificate in PEM bundle |
key_file 中没有 PEM 私钥 |
一条以 No supported data to decode. Input type: PEM 结尾的 OpenSSL 解码错误 |
| 私钥与证书不匹配 | OpenSSL 错误 SSL_CTX_check_private_key:no private key assigned |
etemenanki-app 在构建配置时读取这些文件,即启动时和每次热重载时。只有配置文件内容发生变化才会触发重载,所以磁盘上续期后的证书不会被自动加载。证书续期后,请重启进程或修改配置文件。
出站:服务器名称与证书校验
Section titled “出站:服务器名称与证书校验”出站要决定三件事:作为 SNI 发送的名称、期望在证书中看到的名称(与前者相同),以及信任哪些证书颁发机构。
这个名称在设置了 tls.server_name 时取其值,否则取 server。由于每个能使用传输层的出站都要求设置 server,名称总是存在的。当你用 IP 地址拨号、而证书签发给某个域名时,或者服务器位于按 SNI 路由的前端之后时,请设置 server_name:
[[outbound]]tag = "proxy"protocol = "trojan"server = "203.0.113.10"port = 443
[outbound.stream]network = "tls"
[outbound.stream.tls]server_name = "proxy.example.com"
[outbound.settings]password = "replace-with-a-long-random-password"如果这个名称是 IP 地址,则不发送 SNI,且证书中必须包含该 IP 地址。
证书校验有三种模式:
| 设置 | 信任的证书 | 是否校验主机名 |
|---|---|---|
既不设 ca_file 也不设 allow_insecure(默认) |
系统根证书 | 是 |
ca_file = "/etc/etemenanki/ca.pem" |
系统根证书,加上文件中的每一张证书 | 是 |
allow_insecure = true |
任何证书 | 否 |
系统根证书指 OpenSSL 的默认信任库;环境变量 SSL_CERT_FILE 和 SSL_CERT_DIR 可以改变 OpenSSL 查找它的位置。ca_file 是在这个信任库之上追加,而不是替换它,所以即使设置了 ca_file,使用公共受信证书的服务器仍然能通过校验。对证书由你自己的 CA 签发的服务器,请使用 ca_file。自签名的服务器证书同样可以:把证书本身放进 ca_file,并确保它覆盖客户端要校验的名称。
ca_file 与 allow_insecure 不能同时使用:
outbound proxy: tls.allow_insecure and tls.ca_file cannot both be set协议版本与 ALPN
Section titled “协议版本与 ALPN”TLS 由 OpenSSL 提供。两端都接受 TLS 1.2 和 TLS 1.3,拒绝更旧的版本;对端支持时使用 TLS 1.3。入站使用 Mozilla 的 “intermediate” 加密套件配置。
ALPN 由承载方式固定,无法设置:
| 承载方式 | 出站提供的 ALPN | 入站选择的 ALPN |
|---|---|---|
network = "tls" |
无 | 无 |
带 security = "tls" 的 ws |
http/1.1 |
http/1.1 |
带 security = "tls" 的 grpc |
h2 |
h2 |
如果客户端提供的协议中没有入站认识的,入站不会拒绝,而是在不协商 ALPN 的情况下完成握手。
不可配置的内容
Section titled “不可配置的内容”TLS 表只有上面这五个字段。以下内容都没有对应字段:
- ALPN、加密套件、TLS 版本或椭圆曲线;
- TLS 指纹(uTLS)或 REALITY;
- 证书固定(pinning);
- 客户端证书(双向 TLS),两端都不支持;
- 每个入站使用多张证书,或按 SNI 选择证书。
Xray tlsSettings 中的字段,例如 alpn 或 fingerprint,会作为未知字段被拒绝:
unknown field `alpn`, expected one of `server_name`, `allow_insecure`, `ca_file`, `cert_file`, `key_file`WebSocket
Section titled “WebSocket”network = "ws" 让协议运行在一个 WebSocket 会话中:每次写入都成为一条二进制消息。放在 CDN 或会转发 WebSocket 升级请求的 HTTP 反向代理后面时,应使用这种传输层。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
path | string | 否 | "/" | 升级请求的 HTTP 路径。空路径视为 /,缺少开头的 / 会自动补上,所以 ray 等同于 /ray。?ed=N 查询参数不算路径的一部分:在出站上它开启最多 N 字节(上限 16384)的 early data;在入站上它会被去掉并忽略。入站对请求路径做精确比较,其他路径一律返回 404。 |
host | string | 否 | — | 入站:设置后,请求的 Host 头必须与之相同(不区分大小写,忽略 :port),否则升级请求返回 404;不设置则接受任何 Host。出站:要发送的 Host 头,未设置时依次使用 tls.server_name 和 server;它不影响 TLS 的 SNI。server 是 IPv6 地址时应设置此项,因为裸 IPv6 地址不能直接作为 URI 主机。 |
两端以相同方式规范化路径,所以下面每一对写法是等价的:
| 写法 | 实际使用 |
|---|---|
缺省或 "" |
/ |
ray |
/ray |
/ray?ed=2048 |
/ray,在出站上开启 early data |
/ray?foo=1&ed=2048 |
/ray?foo=1,在出站上开启 early data |
入站将升级请求的路径与自己的路径做区分大小写的精确比较,其他路径的请求一律返回 404 Not Found。它只比较路径,从不比较查询字符串,因此如果入站的 path 保留了 ed 以外的查询参数,就永远匹配不到任何请求。
在入站上,host 是一项可选检查。设置后,升级请求必须带有同名的 Host 头,比较时不区分大小写,并忽略 :port 后缀;Host 不同或缺失的请求会收到 404 Not Found,与路径错误时的响应相同。不设置 host 时,接受任何 Host。
在出站上,host 是要发送的 Host 头。未设置时,出站按以下顺序取第一个已设置的值:
ws.hosttls.server_name,即使security不是"tls"server
ws.host 只设置这个请求头。SNI 和证书中校验的名称仍然来自 tls.server_name 或 server,所以在 CDN 后面通常要两者都设置,如下例所示。
当 server 是一个与 CDN 或反向代理用于路由的站点名称不同的地址时,请设置 host:
[[outbound]]tag = "via-cdn"protocol = "vmess"server = "198.51.100.20" # CDN 边缘节点port = 443
[outbound.stream]network = "ws"security = "tls"
[outbound.stream.ws]path = "/ray"host = "proxy.example.com"
[outbound.stream.tls]server_name = "proxy.example.com"
[outbound.settings]id = "11111111-2222-3333-4444-555555555555"Early data
Section titled “Early data”WebSocket early data 可以省去一次往返:客户端不必等升级完成,而是把代理连接的前几个字节放在升级请求里一起发出。etemenanki-app 使用与 Xray 相同的方案,因此无论哪一端是 Xray 都能互通。
- 出站。 在
path后面加上?ed=N。出站在拨号时就建立 TCP(和 TLS)连接,但会暂缓发送升级请求,直到协议写出第一批字节。其中最多N个字节经 base64url 编码后放在Sec-WebSocket-Protocol头里发送,其余字节随后作为普通消息发送。N的上限是 16384。ed=0或非数字的值会关闭 early data;无论哪种情况,这个参数都会从路径中移除。 - 入站。 无需配置。无论自己的
path是否带?ed=,入站都接受任何客户端放在Sec-WebSocket-Protocol头中的 early data,并在该头携带了 early data 时把它原样回显。无法按 base64 解码的头会被忽略。解码后超过 16 KiB 的 early data 会被拒绝,返回413 Payload Too Large。
Xray 客户端通常写 path = "/ray?ed=2048"。在 etemenanki-app 出站上使用同样的路径,在入站上使用 /ray 或 /ray?ed=2048 均可。
network = "grpc" 把协议承载在 HTTP/2 上的 gRPC stream 中,与 Xray 的 gRPC 传输兼容。它适合 HTTP/2 前端以及能代理 gRPC 的 CDN。
| 键 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
service_name | string | 视情况 | — | network = "grpc" 时必填(否则报 grpc stream needs grpc.service_name)。隧道路径为 /SERVICE/Tun 和 /SERVICE/TunMulti。名称原样拼进路径,所以不要带斜杠。两端必须使用相同的名称。 |
authority | string | 否 | — | 仅用于出站。每个请求的 HTTP/2 :authority,未设置时依次使用 tls.server_name 和 server;它不影响 TLS 的 SNI。server 是 IPv6 地址时应设置此项,因为裸 IPv6 地址不能直接作为 URI authority。入站不检查 authority,也忽略此项。 |
服务名与路径
Section titled “服务名与路径”network = "grpc" 时,两端都必须设置 service_name:
inbound vmess-in: grpc stream needs grpc.service_name它会生成两个请求路径。以 service_name = "tunnel" 为例:
| 路径 | Xray 中的名称 | 入站 | 出站 |
|---|---|---|---|
/tunnel/Tun |
gun 模式,每条 gRPC 消息一个 Hunk |
接受 | 始终使用 |
/tunnel/TunMulti |
multi 模式(multiMode: true),每条消息一批 MultiHunk |
接受 | 从不使用 |
入站同时服务这两个路径,所以 Xray 客户端无论是否开启 multiMode 都能连接。对其他路径的请求会以 REFUSED_STREAM 重置。入站不检查 :authority。
出站把每条隧道作为一个发往 /SERVICE/Tun 的 POST 请求发送,并带有:
:authority,取grpc.authority、tls.server_name(即使不使用 TLS)和server中第一个已设置的值;content-type: application/grpc和te: trailers;- 一个固定的桌面版 Chrome
user-agent,无法修改。
出站拨号的每个流都会建立自己的 TCP 连接和 HTTP/2 连接;流不会在共享连接上多路复用。
超时、保活与上限
Section titled “超时、保活与上限”以下数值都不可配置。
| 项目 | 值 | 适用范围 |
|---|---|---|
| TCP keepalive | 静默 120 秒后发送第一个探测包,之后每 30 秒一次;连续 3 个探测无响应则断开连接 | 入站接受的每条 TCP 连接,以及出站拨往服务器的每条连接 |
| 传输层握手 | TLS 握手、WebSocket 升级或 HTTP/2 preface 共 10 秒,包含 TLS | 入站 |
| 协议握手 | 在传输层握手之后开始计时的另一个 10 秒限制;见限制 | 入站 |
| WebSocket ping | 60 秒内没有 WebSocket 流量时发送一个 Ping |
两端 |
| WebSocket 空闲 | 300 秒内既没有收到任何帧、也没有发送任何数据时,会话结束 | 两端 |
| WebSocket 消息大小 | 每条消息和每个帧最大 1 MiB | 两端 |
| WebSocket early data | 16 KiB | 两端 |
| gRPC 连接空闲 | 没有打开的 stream 的 HTTP/2 连接在 300 秒后关闭 | 入站 |
| gRPC ping | 每 60 秒发送一个 PING;如果 PONG 超过 20 秒未返回,则关闭连接 |
入站 |
| gRPC 消息大小 | 每条消息最大 1 MiB | 两端 |
| HTTP/2 stream | 每条连接最多 256 个并发 stream | 入站 |
| HTTP/2 流量控制 | 每个 stream 窗口 4 MiB,每条连接 16 MiB,最大帧 256 KiB | 两端 |
keepalive 和 ping 机制用于回收对端已经消失却没有关闭的连接,例如切换了网络的手机。它们不会关闭只是空闲、但仍在响应的连接。
| 错误 | 原因 | 解决方法 |
|---|---|---|
security = "tls" is not valid with network = "tcp" |
使用了 Xray 对 TLS over TCP 的写法 | 改写为 network = "tls",并去掉 security |
unknown stream network "WS" |
大小写错误、带空格,或承载方式不受支持 | 严格使用 tcp、tls、ws 或 grpc |
unknown stream security "reality" (expected "tls" or "none") |
使用了 REALITY、XTLS,或拼写错误 | 使用 tls 或 none |
protocol socks does not support stream network "ws" |
在无法承载传输层的协议上设置了传输层 | 删除 stream 表,或把传输层移到能使用它的协议上 |
protocol vless over a unix socket does not support stream network "ws" |
在 Unix socket 监听器上设置了传输层 | 改为监听 IP 地址,或去掉传输层 |
tls stream needs tls.cert_file |
入站使用 TLS 但没有证书 | 在 [inbound.stream.tls] 中添加 cert_file 和 key_file |
no certificate in PEM bundle |
cert_file 中没有 PEM 证书 |
检查路径,并确认 cert_file 和 key_file 没有写反 |
no private key assigned |
key_file 与 cert_file 中的第一张证书不对应 |
使用与证书一同签发的私钥,并把叶子证书放在 cert_file 的最前面 |
No such file or directory (os error 2) |
证书、私钥或 CA 路径错误 | 检查 stream 表中的每个文件路径 |
grpc stream needs grpc.service_name |
network = "grpc" 但没有服务名 |
在两端把 service_name 设为相同的值 |
tls stream needs tls.server_name or server、ws stream needs ws.host or server |
出站没有设置 server;stream 的检查先于 server 本身的检查 |
设置 server 和 port |
tls.allow_insecure and tls.ca_file cannot both be set |
同时设置了两种校验覆盖方式 | 保留 ca_file,删除 allow_insecure |
no certificate in CA PEM bundle |
ca_file 中没有 PEM 证书 |
让 ca_file 指向包含 CA 证书的 PEM 文件 |
unknown field `headers`, expected `path` or `host` |
在 ws 表中使用了 Xray 的字段 |
用 host 代替 headers.Host |
以下故障能通过 --test,只会在运行中的连接上出现:
- 两端的路径、
Host或服务名不一致。 WebSocket 入站返回404,gRPC 入站重置 stream,客户端记录一次拨号失败。请逐字符比较path、host和service_name。 - 客户端不接受的证书。 客户端记录一条 TLS 校验错误。请确认证书覆盖了客户端校验的名称(
server_name或server),并且cert_file中的证书链包含了中间证书。 ws或grpc出站使用 IPv6server,却没有设置ws.host或grpc.authority。 每次拨号都会失败;见 gRPC 一节末尾的警告,并显式设置 host。
更多跨主题的易错点汇总在常见陷阱中,Xray streamSettings 的逐字段对照见从 Xray 迁移。