基于 gRPC 和 TLS 的 VMess
本方案用两份 etemenanki-app 搭建一套完整的 VMess 部署。服务端监听 TCP 443 端口,接受承载在 gRPC 中、再由 TLS 包裹的 VMess,使用由公共证书颁发机构签发的证书。客户端运行在你自己的机器上,在 127.0.0.1:1080 上提供 SOCKS 代理,并通过同一条 gRPC over TLS 隧道把每个连接发往服务端。
在线路上,这种连接看起来就是对你的域名 443 端口发起的一次普通 HTTP/2 gRPC 调用。因此,当两台机器之间的路径受到过滤,或者你想在服务端前面放一层 CDN 时,这种布局是常见的选择。请按顺序执行各步骤,每一步最后都有一项检查。
flowchart LR A["浏览器或 curl"] -->|"SOCKS5"| S["客户端入站 socks-in 127.0.0.1:1080"] S --> O["客户端出站 proxy (vmess)"] O -->|"TLS、HTTP/2、gRPC,发往 proxy.example.com:443"| I["服务端入站 vmess-in"] I --> D["服务端出站 direct (freedom)"] D --> T["目标地址"]
两台机器之间的每个连接都经过四层。由外到内依次是:
| 层 | 由哪些配置决定 | 在本方案中的作用 |
|---|---|---|
| TCP | 客户端的 server、port;服务端的 listen、port |
在 443 端口上承载全部内容。 |
| TLS | [stream].security = "tls" 和 [stream.tls] |
加密连接,并用证书证明服务端的身份。ALPN 为 h2。 |
| HTTP/2 和 gRPC | [stream].network = "grpc" 和 [stream.grpc] |
把隧道封装成一次对 /<service_name>/Tun 的 gRPC 调用。 |
| VMess | protocol = "vmess",以及 [inbound.settings] 或 [outbound.settings] |
通过 UUID 和时间戳认证用户,指明目标地址,并对载荷再加密一次。 |
- 一个域名,其 A 记录(使用 IPv6 时还有 AAAA 记录)指向服务端。本页使用
proxy.example.com,请在所有地方替换成你自己的域名。 - 服务端的 TCP 443 端口已开放且空闲:不能已有 Web 服务器在监听它。绑定 1024 以下的端口需要 root 权限或
CAP_NET_BIND_SERVICEcapability;服务配置见运行 etemenanki-app。 - 两台机器上都已安装 etemenanki-app。二进制文件和建议的
/etc/etemenanki/目录布局见安装。 - 服务端上有一个 ACME 客户端,例如 certbot、acme.sh 或 lego,用来申请免费证书。
- 两台机器的时钟已同步。客户端时钟与服务端相差超过 120 秒时,VMess 会拒绝该客户端,所以现在就开启 NTP(见保持时钟同步)。
- 客户端机器上有
curl,用于最后的测试。
两个文件都是完整的;证书文件就位后,它们可以原样通过 --test。标签页之后的表格列出了两份文件中必须一致的值;其中 UUID、服务名和域名需要换成你自己的。
# The server side of the "VMess over gRPC and TLS" recipe: a VMess inbound# served as gRPC over TLS on port 443, with a direct exit.# Replace the certificate paths, the service name and the UUID before you use it.
[log]level = "info"
[[inbound]]tag = "vmess-in"protocol = "vmess"listen = "0.0.0.0"port = 443
[inbound.stream]network = "grpc"security = "tls"
# The client must use exactly the same service name.[inbound.stream.grpc]service_name = "tunnel"
# The certificate for proxy.example.com, as your ACME client writes it.[inbound.stream.tls]cert_file = "/etc/etemenanki/certs/fullchain.pem"key_file = "/etc/etemenanki/certs/privkey.pem"
[inbound.settings]users = [ { id = "11111111-2222-3333-4444-555555555555", email = "alice@example.com" },]
[[outbound]]tag = "direct"protocol = "freedom"
[[outbound]]tag = "block"protocol = "blackhole"
[route]default = "direct"
# Refuse requests for private, loopback and link-local addresses. This rule# only sees requests that name an IP: a domain is not resolved before routing.[[route.rule]]outbound = "block"cidr = [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16", "fc00::/7", "fe80::/10", "::1/128",]# The client side of the "VMess over gRPC and TLS" recipe: a local SOCKS# proxy on 127.0.0.1:1080 that sends every connection to the VMess server.# Replace the server name, the service name and the UUID with the server's.
[log]level = "info"
[[inbound]]tag = "socks-in"protocol = "socks"listen = "127.0.0.1"port = 1080
[[outbound]]tag = "proxy"protocol = "vmess"server = "proxy.example.com"port = 443
[outbound.stream]network = "grpc"security = "tls"
# Must equal the server's service_name.[outbound.stream.grpc]service_name = "tunnel"
# The name the server's certificate is issued for.[outbound.stream.tls]server_name = "proxy.example.com"
[outbound.settings]id = "11111111-2222-3333-4444-555555555555"security = "aes-128-gcm"
[route]default = "proxy"| 值 | 服务端 | 客户端 | 规则 |
|---|---|---|---|
| 用户 UUID | [inbound.settings].users[].id |
[outbound.settings].id |
客户端的 id 必须是服务端用户之一。 |
| 服务名 | [inbound.stream.grpc].service_name |
[outbound.stream.grpc].service_name |
必须逐字节相同,包括大小写。 |
| 域名 | cert_file 所指证书中的名称 |
[outbound.stream.tls].server_name(或 server) |
证书必须对客户端所校验的名称有效。 |
| 端口 | port = 443 |
port = 443 |
客户端连接服务端监听的端口。 |
| 传输层 | network = "grpc"、security = "tls" |
network = "grpc"、security = "tls" |
两端都必须使用 gRPC over TLS。 |
服务端还带有一个 block 出站和一条规则,把私有地址、回环地址和链路本地地址发往该出站,其余流量都经 direct 发出。这条规则只匹配以 IP 地址指定目标的请求:路由在匹配之前不会解析域名,所以客户端请求一个解析到私有地址的域名时,不会被拦下。curl --socks5-hostname 以及通过代理解析域名的浏览器总是发送域名。若要阻止客户端访问内部主机,还需要屏蔽内部域名,或者用主机防火墙限制服务端;匹配方式见路由。
-
为你的域名申请证书。 用 ACME 客户端为
proxy.example.com申请证书。任何不需要 443 端口的验证方式都可以:HTTP-01 在 80 端口上应答,DNS-01 完全不需要端口。TLS-ALPN-01 需要 443 端口,而该端口将由 etemenanki-app 占用,因此服务端运行后它会失败。你需要从 ACME 客户端拿到两个 PEM 文件:
- 完整证书链:你的证书,后面跟着中间证书(certbot 称之为
fullchain.pem); - 未加密的私钥(certbot 称之为
privkey.pem)。
请使用完整证书链,而不是单独的证书。etemenanki-app 会把文件中的第一张证书作为叶证书发送,后面的每一张都作为中间证书发送;缺少中间证书时,依据系统信任库校验的客户端会报
certificate verify failed。 - 完整证书链:你的证书,后面跟着中间证书(certbot 称之为
-
把文件放到服务端。 将它们复制到服务端配置中指定的路径,并让私钥只对服务可读:
终端窗口 sudo install -d -m 0755 /etc/etemenanki/certssudo install -m 0644 fullchain.pem /etc/etemenanki/certs/fullchain.pemsudo install -m 0600 privkey.pem /etc/etemenanki/certs/privkey.pem这些命令适用于以 root 运行的服务端。运行 etemenanki-app 中的 systemd unit 则以用户
etemenanki运行;这种情况下,请用-o root -g etemenanki -m 0640安装私钥,让服务能够读取。你也可以不复制文件,直接让cert_file和key_file指向 ACME 客户端自己的输出文件;无论哪种方式,都要写绝对路径。 -
确定两端共用的值。 为用户生成一个 UUID:
终端窗口 uuidgen把它填入服务端的
users和客户端的id,替换11111111-2222-3333-4444-555555555555。选一个服务名,写进两端的service_name,并把客户端中的proxy.example.com换成你的域名。服务端文件在服务端上保存为/etc/etemenanki/config.toml,客户端文件在你的机器上保存为/etc/etemenanki/config.toml。 -
检查两个文件。 在每台机器上执行:
终端窗口 etemenanki-app --test -c /etc/etemenanki/config.toml两边都会输出
Configuration OK.并以状态码0退出。在服务端上,--test还会读取并解析证书和私钥,所以路径错误或私钥与证书不匹配会在这里暴露,而不是等到第一个连接。失败时的输出见常见错误。 -
启动服务端。 先在前台运行,以便查看日志:
终端窗口 sudo etemenanki-app -c /etc/etemenanki/config.tomlINFO etemenanki_app::instance: inbound vmess-in listening on 0.0.0.0:443测试通过后,改为以服务方式运行,见运行 etemenanki-app。
-
启动客户端。 在你的机器上执行:
终端窗口 etemenanki-app -c /etc/etemenanki/config.tomlINFO etemenanki_app::instance: inbound socks-in listening on 127.0.0.1:1080此时客户端还不会向服务端建立任何连接,直到第一个应用使用 SOCKS 端口时才会连接。
-
通过隧道发送一个请求。 在客户端机器上另开一个终端:
终端窗口 curl --socks5-hostname 127.0.0.1:1080 https://example.comcurl会输出 Example Domain 页面的 HTML。要确认流量确实从服务端出去,可以用同样的方式请求任意一个回显公网 IP 地址的服务:它输出的是服务端的地址,而不是你的。如果失败,失败的形式能告诉你连接走到了哪一步:
- curl 无法完成 SOCKS5 连接。 客户端连不上服务端:TCP 连接或 TLS 握手失败,客户端因此拒绝了 SOCKS 请求。
- curl 拿到的连接没有数据就关闭了,例如对普通
http://URL 报curl: (52) Empty reply from server,或对https://报SSL_ERROR_SYSCALL之类的 TLS 错误。TCP、TLS 和 HTTP/2 都正常,但服务端拒绝了 gRPC 流或 VMess 请求:请检查服务名、UUID 和时钟。
要查看详情,在两台机器的
[log]下设置level = "debug",重启两端,再对照常见错误。
把浏览器或其他应用的代理设为 SOCKS5 代理 127.0.0.1:1080。SOCKS 入站也接受 UDP(其 udp 设置默认为 true),而 VMess 出站会把每个 UDP 流放在各自的隧道连接中承载,所以来自 SOCKS5 客户端的 DNS 和其他 UDP 流量同样可用。该入站的设置见 SOCKS。
本方案用到的设置
Section titled “本方案用到的设置”下面的每个键都会被校验:拼错的键,包括 Xray 的驼峰式名称如 serviceName,都会报 unknown field `serviceName`, expected `service_name` or `authority` 。各种 network 的 stream 设置见传输层,[inbound.settings] 和 [outbound.settings] 见 VMess。
[stream]
Section titled “[stream]”| 键 | 类型 | 默认值 | 可选值 | 说明 |
|---|---|---|---|---|
network |
string(枚举) | "tcp" |
tcp、tls、ws、grpc |
区分大小写。grpc 要求设置 [stream.grpc].service_name。其他值会报 unknown stream network。 |
security |
string(枚举) | 无 | tls、none 或空 |
当 network = "grpc" 时,tls 会在 HTTP/2 之下加上 TLS;省略则在明文 TCP 上运行 gRPC。其他任何值,包括 TLS、reality 和 xtls,都会报 unknown stream security "…" (expected "tls" or "none")。 |
不带 security = "tls" 的 gRPC 在两端都能通过 --test,所以省略它的客户端会构建出一个明文拨号器。对本方案的服务端,它会在连接时失败,服务端记录 wrong version number。在本方案中,请始终在两端都设置 security = "tls"。
[stream.grpc]
Section titled “[stream.grpc]”| 键 | 类型 | 默认值 | 服务端 | 客户端 |
|---|---|---|---|---|
service_name |
string | 无;network = "grpc" 时必填 |
服务端响应对 /<service_name>/Tun 和 /<service_name>/TunMulti 的请求,拒绝其他所有路径。 |
客户端总是请求 /<service_name>/Tun。 |
authority |
string | tls.server_name,其次 server |
忽略:服务端不检查客户端发送的 authority。 | 请求的 HTTP/2 :authority,相当于 HTTP 的 Host 头。 |
缺少 service_name 会报 inbound vmess-in: grpc stream needs grpc.service_name(客户端上为 outbound proxy: …)。空字符串能通过检查,但构建出的路径是 //Tun,请使用一个真正的名称。etemenanki-app 会把名称原样插入路径,所以请写 tunnel 这样的普通名称,不要带斜杠。
[stream.tls]
Section titled “[stream.tls]”| 键 | 类型 | 默认值 | 服务端 | 客户端 |
|---|---|---|---|---|
cert_file |
path | 无 | 必填:PEM 证书链,叶证书在前。省略会报 tls stream needs tls.cert_file。 |
忽略。 |
key_file |
path | 无 | 必填:未加密的 PEM 私钥,与叶证书匹配。省略会报 tls stream needs tls.key_file。 |
忽略。 |
server_name |
string | server |
忽略。 | 作为 SNI 发送并用于校验证书的名称。 |
allow_insecure |
bool | false |
忽略。 | true 会关闭证书链和名称校验。使用正式证书时请保持关闭。 |
ca_file |
path | 无 | 忽略。 | 额外的 PEM CA 证书,添加到系统信任库之上。不能与 allow_insecure 同时使用。 |
两端都使用 OpenSSL,最低版本为 TLS 1.2,因此两端都支持时会协商出 TLS 1.3。对于 gRPC,两端都提供并选择 ALPN 协议 h2;ALPN、密码套件和客户端指纹都没有对应的配置键。
客户端如何选择名称
Section titled “客户端如何选择名称”客户端使用三个不同的名称,省略某个时会依次回退到下一个:
| 用途 | 首选 | 回退 | 最终回退 |
|---|---|---|---|
| TCP 连接的目标地址 | server |
无 | 无 |
| TLS 服务器名称(SNI)和证书校验 | [stream.tls].server_name |
server |
无 |
HTTP/2 :authority |
[stream.grpc].authority |
[stream.tls].server_name |
server |
像示例中那样,当 server 就是你的域名时,server_name 和 authority 都可以省略:它们默认就是同一个名称。三者不同时,请显式设置:
server是 IP 地址。 把server_name设为证书中的域名。否则客户端会用 IP 地址校验证书,而为域名签发的证书并不覆盖该 IP 地址。server指向 CDN 或其他前置代理。 把server_name设为你自己的域名;如果前置代理按:authority路由,authority也设为你的域名。见部署在 CDN 之后。
客户端用 etemenanki-app 自己的解析器解析 server,使用哪个 IP 地址族由出站上的 address_family 决定;见出站和 DNS。
服务名必须一致
Section titled “服务名必须一致”服务名是请求路径中唯一由你选择的部分,服务端正是靠它把隧道请求与到达 443 端口的其他请求区分开。客户端请求的名称不同时,TLS 和 HTTP/2 握手仍会成功;随后服务端以 HTTP/2 错误 REFUSED_STREAM 拒绝这一个流,并且不为此记录任何错误,即使在 debug 级别也不会。
客户端会在 debug 级别记录这次拒绝:
DEBUG etemenanki_app::serve: socks connection from Some(127.0.0.1) ended: stream error received: refused stream before processing any application logic而它背后的应用看到的是一个没有数据就关闭的连接。如果客户端失败而服务端日志毫无动静,先比较两端的 service_name 值。
保持时钟同步
Section titled “保持时钟同步”每个 VMess 连接都带有时间戳,服务端只接受与自身时钟相差 120 秒以内的时间戳,快慢均可。客户端或服务端的时钟漂移超出这个范围时,失败方式与 UUID 错误完全相同,服务端在 debug 级别记录:
DEBUG etemenanki_app::serve: vmess connection from Some(203.0.113.7) ended: proxy core: vmess: unknown user or invalid auth id在两台机器上都运行 NTP(timedatectl status 应显示 System clock synchronized: yes)。时区没有影响,只有绝对时钟才重要。VMess 页面中的时钟窗口解释了这项检查,并给出了逐步的修复方法。
部署在 CDN 之后
Section titled “部署在 CDN 之后”由于隧道是 HTTP/2 上的标准 gRPC 调用,你可以在客户端和服务端之间放置支持 gRPC 的 CDN 或反向代理。前置代理终结客户端的 TLS 连接,再自行与你的服务端建立 HTTP/2 连接,并转发 gRPC 流。
-
在前置代理上开启 gRPC。 许多 CDN 只有在你为域名开启 gRPC 后才会转发它,有些只在特定端口上接受 gRPC;请查阅服务商的文档。前置代理必须以 HTTP/2 over TLS 连接你的服务端;降级到 HTTP/1.1 的前置代理无法承载 gRPC。
-
在服务端保留一张前置代理认可的证书。 前置代理通过 TLS 连接你的服务端,并按它自己的设置校验证书。为你的域名签发的公共证书在严格模式下也可用;有些 CDN 还会为这段链路签发源站证书。
-
让客户端指向前置代理。 把
server设为前置代理的主机名或地址,server_name仍设为你自己的域名。这样客户端会把你的域名作为 SNI 和:authority发送,前置代理正是据此找到你的站点。只有当前置代理期望的主机名与 TLS 名称不同时,才需要单独设置authority。
服务端从不检查收到的 :authority,所以前置代理转发过来的任何主机名都会被接受。
与 Xray 互通
Section titled “与 Xray 互通”服务端接受通过 gRPC 和 TLS 连接的 Xray 客户端,客户端也可以用相同的传输层连接 Xray VMess 服务端。Etemenanki 的测试套件会用真实的 Xray 客户端测试这种服务端布局,并用真实的 Xray 服务端测试 gRPC over TLS 客户端传输层。在两者之间迁移配置时,按下表对应各个键:
| Xray JSON | etemenanki-app TOML | 说明 |
|---|---|---|
入站 settings.clients[].id |
[inbound.settings].users[].id |
每个用户只接受 id。alterId、email 和 level 会被拒绝。 |
出站 settings.vnext[].address、port |
server、port |
|
出站 vnext[].users[].id |
[outbound.settings].id |
|
出站 vnext[].users[].security |
[outbound.settings].security |
aes-128-gcm、chacha20-poly1305 或 auto,不区分大小写。这里的 auto 总是表示 AES-128-GCM。none 和 zero 会被拒绝,服务端也会拒绝使用它们的 Xray 客户端。 |
vnext[].users[].alterId |
无 | Xray 端必须为 0;etemenanki-app 只支持 AEAD VMess。 |
streamSettings.network: "grpc" |
[stream].network = "grpc" |
|
streamSettings.security: "tls" |
[stream].security = "tls" |
不支持 reality。 |
grpcSettings.serviceName |
[stream.grpc].service_name |
只支持普通名称。Xray 的自定义路径形式,即以 / 开头的 serviceName,没有对应写法。 |
grpcSettings.authority |
[stream.grpc].authority |
仅客户端。 |
grpcSettings.multiMode |
无 | 服务端两种模式(Tun 和 TunMulti)都接受。客户端总是使用 Tun,所有 Xray 服务端都接受这种模式。 |
grpcSettings.user_agent、idle_timeout、health_check_timeout、permit_without_stream、initial_windows_size |
无 | 在 etemenanki-app 内部固定。 |
tlsSettings.serverName |
[stream.tls].server_name |
|
tlsSettings.allowInsecure |
[stream.tls].allow_insecure |
|
tlsSettings.certificates[].certificateFile、keyFile |
[stream.tls].cert_file、key_file |
只支持文件;不支持内联证书。 |
tlsSettings.alpn、fingerprint、pinnedPeerCertSha256 |
无 | gRPC over TLS 的 ALPN 始终为 h2。 |
连接本方案服务端的 Xray 客户端需要这样一个出站:
{ "protocol": "vmess", "settings": { "vnext": [ { "address": "proxy.example.com", "port": 443, "users": [ { "id": "11111111-2222-3333-4444-555555555555", "alterId": 0, "security": "aes-128-gcm" } ] } ] }, "streamSettings": { "network": "grpc", "security": "tls", "tlsSettings": { "serverName": "proxy.example.com" }, "grpcSettings": { "serviceName": "tunnel" } }}Xray 客户端连接本服务端时可以开启 mux("mux": { "enabled": true }):VMess 入站会自动处理 mux.cool 和 XUDP。配置其他部分的差异见从 Xray 迁移。
- 客户端上每个流一条隧道连接。 etemenanki-app 客户端为每个被代理的 TCP 连接和每个 UDP 流新建一条 TCP 连接、一个 TLS 会话和一条 HTTP/2 连接,并在流结束时关闭。它不会在流之间保持共享的 HTTP/2 连接,所以每个新流都要与服务端进行一次 TCP 握手和一次 TLS 握手。
- 服务端上每条连接承载多个流。 服务端在一条 HTTP/2 连接上最多接受 256 个并发 gRPC 流,Xray 客户端在多个流之间共享连接时用的正是这种方式。单条 gRPC 消息最大为 1 MiB。
- 握手时间。 客户端必须在连接后 10 秒内完成 TLS 和 HTTP/2 握手(否则服务端记录
grpc handshake timed out)。在每个 gRPC 流上,服务端会关闭 10 秒内没有发送任何数据的流(inbound handshake timed out after 10s);首批字节到达后,VMess 请求必须在 10 秒内完整到达。 - 空闲连接。 服务端会断开 300 秒内没有承载任何流的 HTTP/2 连接。它还每 60 秒发送一次 HTTP/2 PING,如果应答超过 20 秒才到就断开连接,以此发现那些流仍处于打开状态却未关闭就消失的客户端。被代理的连接本身在双向都没有流量 300 秒后关闭。
这些限制以及各入站的连接数上限,汇总在限制中。
etemenanki-app 在构建配置时读取证书和私钥,也就是在启动时和热重载时。它不监视证书文件,而热重载只会在配置文件的字节发生变化时触发,所以仅替换证书不会影响正在运行的服务端。
每次续期后,让 ACME 客户端的 deploy 或 reload hook 执行以下任一操作:
- 重启服务端。 使用运行 etemenanki-app 中的 unit 时,执行
systemctl restart etemenanki(换成你的 unit 名称)。先运行etemenanki-app --test -c /etc/etemenanki/config.toml:它会像启动时一样读取新的证书和私钥,所以文件有问题时能在旧进程仍在提供服务时发现。 - 修改配置文件。 对其字节的任何改动,例如重写一行注释,都会触发热重载,而热重载会重新读取证书和私钥。如果新文件有问题,热重载会以
reload: build failed, keeping current config: …失败,旧证书继续使用。修改证书或 geodata 文件中给出了一行即可实现的 hook。
两种方式都会断开当时已打开的连接;客户端在下一个流时重新连接。
配置错误在 --test 下以 configuration invalid: 开头,启动时以 failed to start: 开头,热重载时以 reload: build failed, keeping current config: 开头(TOML 错误则为 reload: parse failed, …)。连接错误只在 debug 级别出现:在 [log] 下设置 level = "debug" 并重启,因为热重载不会改变日志级别。客户端上的形式为 socks connection from … ended: …;服务端上,TLS 和 HTTP/2 失败为 inbound transport failed: …,VMess 失败为 vmess connection from … ended: …。
| 消息 | 出现位置 | 原因 | 解决方法 |
|---|---|---|---|
grpc stream needs grpc.service_name |
--test,任一端 |
network = "grpc" 但没有 [stream.grpc].service_name |
添加服务名。 |
unknown field `serviceName`, expected `service_name` or `authority` |
--test,任一端 |
使用了 Xray 的键名 | 写成 service_name。 |
tls stream needs tls.cert_file(或 tls.key_file) |
--test,服务端 |
security = "tls" 但缺少证书或私钥路径 |
在 [inbound.stream.tls] 下添加两个路径。 |
No such file or directory (os error 2) |
--test,服务端 |
证书或私钥路径不存在;消息中不会给出文件名 | 检查两个路径,并写成绝对路径。 |
no certificate in PEM bundle |
--test,服务端 |
cert_file 指向的文件中没有证书,例如指向了私钥 |
让 cert_file 指向完整证书链 PEM 文件。 |
SSL_CTX_check_private_key:no private key assigned(包含在一条更长的 OpenSSL 错误中) |
--test,服务端 |
key_file 与证书不匹配 |
使用与证书一同签发的私钥。 |
unknown stream security "TLS" (expected "tls" or "none") |
--test,任一端 |
使用了大写,或 reality 这类 Xray 独有的值 |
写成 security = "tls"。 |
tls.allow_insecure and tls.ca_file cannot both be set |
--test,客户端 |
同时设置了两种校验覆盖方式 | 私有 CA 保留 ca_file;公共证书两者都不要设置。 |
stream error received: refused stream before processing any application logic |
客户端,debug |
客户端的 service_name 与服务端不同 |
让两者一致。这种情况下服务端不记录任何日志。 |
certificate verify failed |
客户端,debug |
证书不覆盖 server_name、已过期,或缺少中间证书 |
检查 server_name、续期证书,或在服务端使用完整证书链。 |
sslv3 alert bad certificate |
服务端,debug |
客户端拒绝了服务端的证书 | 见上面的 certificate verify failed。 |
wrong version number |
服务端,debug |
客户端没有使用 TLS 连接:客户端缺少 security = "tls" |
在客户端的 [outbound.stream] 中添加 security = "tls"。 |
vmess: unknown user or invalid auth id |
服务端,debug |
UUID 错误,或时钟相差超过 120 秒 | 比较两端的 UUID,再检查时钟。 |
failed to connect to any address (…: Connection refused (os error 111)) |
客户端,debug |
服务端端口上没有程序监听,或被防火墙拒绝 | 确认服务端正在运行,且 443 端口已开放。 |
unknown vmess security "none" |
--test,客户端 |
VMess 正文加密方式为 none 或 zero |
使用 aes-128-gcm、chacha20-poly1305 或 auto。 |