跳转到内容

Hysteria 2

Hysteria 2 是运行在 QUIC 之上的代理协议,因此走的是 UDP 而不是 TCP。在没有凭据的人看来,Hysteria 2 服务端就是一个 HTTP/3 Web 服务器:它以 ALPN h3 完成 TLS 1.3 握手,并用一个普通页面回应请求。客户端在每个 QUIC 连接上发送一次 HTTP/3 请求来证明身份,此后每个被代理的 TCP 连接都是该连接上的一个 QUIC 流,每个 UDP 包都是该连接上的一个 QUIC 数据报。

etemenanki-app 实现了两端:

  • 作为入站([[inbound]] 下的 protocol = "hysteria2"),它在一个 UDP 端口上为 Hysteria 2 客户端(包括上游的参考客户端)提供服务;
  • 作为出站([[outbound]] 下的 protocol = "hysteria2"),它是一个 Hysteria 2 客户端,把 TCP 和 UDP 流发往 Hysteria 2 服务端。

协议名也可以写作别名 hysteria 和 hy2,但只接受小写。两者都表示 Hysteria 2,不支持 Hysteria 1。katana 的 Hysteria 2 节点使用同一个监听器,但设置来自 katana 自己的节点配置,详见 katana 指南。

入站 出站
承载方式 一个 UDP 端口上的 QUIC 连向一个 UDP 端口的 QUIC
TLS rustls,仅 TLS 1.3,ALPN h3,相关键写在 [inbound.settings] 中 rustls,相关键写在 [outbound.settings] 中
凭据 一个共享的 password,或一张 users 表,以 user:pass 形式发送 一个 password 字符串
TCP 支持 支持
UDP 仅在 udp = true 时支持(默认关闭) 支持,前提是服务端中继 UDP
混淆 salamander salamander
传输层([.stream]) 拒绝 拒绝
Unix socket 监听器 拒绝 不适用
负载均衡器成员 不适用 拒绝
sequenceDiagram
    participant C as 客户端
    participant S as Hysteria 2 入站
    participant O as 路由选中的出站
    C->>S: QUIC 握手,TLS 1.3,ALPN h3
    C->>S: 带 Hysteria-Auth 头的 HTTP/3 POST /auth
    alt 凭据匹配
        S-->>C: 状态码 233,以及是否中继 UDP
        C->>S: 每个 TCP 连接一个 QUIC 流
        S->>O: 为每个目标路由并拨号
        C->>S: UDP 包作为 QUIC 数据报(若 udp = true)
    else 凭据错误,或任何其他请求
        S-->>C: 伪装响应,默认 404
    end

认证在每个 QUIC 连接上进行一次,而不是每个被代理的连接一次。不是有效认证的请求(包括凭据错误的请求)得到的回应,与请求任何其他 URL 完全相同:都是伪装响应。

下面这一对配置中,服务端使用一个共享密码并开启 UDP;本地客户端开放一个 SOCKS5 端口,把所有流量都经服务端发出。

server.toml
[[inbound]]
tag = "hy2-in"
protocol = "hysteria2"
listen = "0.0.0.0" # 默认值是 127.0.0.1
port = 443 # UDP 端口
[inbound.settings]
cert_file = "/etc/etemenanki/cert.pem"
key_file = "/etc/etemenanki/key.pem"
password = "replace-with-a-long-random-password"
udp = true
[[outbound]]
tag = "direct"
protocol = "freedom"

启动这一对配置的步骤:

  1. 为服务端的域名申请证书。公共 CA(例如 Let’s Encrypt)签发的证书无需额外设置就能被所有客户端接受。证书必须包含客户端所连接名称的 subjectAltName,见证书。

  2. 把两个文件中的密码替换成同一个随机值,例如 openssl rand -base64 24 的输出。

  3. 检查两个文件。--test 会读取并解析证书和私钥,因此能发现文件缺失或私钥与证书不匹配。

    终端窗口
    etemenanki-app --test -c server.toml
    etemenanki-app --test -c client.toml
  4. 在服务端防火墙和云厂商的安全组中放行 UDP 端口。放行 TCP 443 的规则并不包括它。以 ufw 为例:

    终端窗口
    ufw allow 443/udp
  5. 先启动服务端,再启动客户端,然后通过客户端的 SOCKS 端口发送请求:

    终端窗口
    curl -x socks5h://127.0.0.1:1080 https://example.com/

客户端的出站采用惰性连接:在第一个流被路由到它之前不会拨号,所以密码或证书错误会在第一次请求时出现在客户端日志中,而不是在启动时。Hysteria 2 实践搭建了一套使用按用户凭据和混淆的完整部署。

入站使用 [[inbound]] 的通用键(tag、listen、port、sniffing),见入站。它自己的键写在 [inbound.settings] 中:

键类型必填默认值说明
cert_filepath是—QUIC 监听器出示的 PEM 证书链,叶子证书在前。构建配置时用 rustls 读取并解析,所以 --test 能发现文件缺失或无法解析。监听器本身接受不带 subjectAltName 的证书,但基于 rustls 的客户端(包括 hysteria2 出站)和当前的上游客户端都会拒绝,所以证书里要有 DNS 或 IP 类型的 SAN。
key_filepath是—cert_file 对应的 PEM 私钥(PKCS#8、PKCS#1 或 SEC1)。可以和 cert_file 是同一个文件。两者缺一会报 hysteria2 needs both cert_file and key_file。
passwordstring视情况—所有客户端共用的一个凭据。password 和 users 必须且只能设置一个。不能为空,并且应只含可打印 ASCII 字符:含其他字符的凭据永远无法匹配。客户端把它原样作为 auth 字符串发送。
usersarray of tables视情况—按用户区分的凭据,格式为 { user, pass, email }。password 和 users 必须且只能设置一个;空列表视为未设置。客户端以 user:pass 作为 auth 字符串发送。
users[].userstring是—用户名。不能为空,应只含可打印 ASCII 字符,也不能包含 :,因为线路上用它分隔用户名和密码。比较时不区分大小写(仅限 ASCII),所以仅大小写不同的两个用户名会被拒绝。
users[].passstring是—用户的密码。不能为空,并且应只含可打印 ASCII 字符。按原样精确比较,可以包含 :,因为服务端在第一个冒号处拆分凭据。
users[].emailstring否""随该用户的流一起传递、用作其用户名的标签。为空时改用 user。它不会在线路上发送;etemenanki-app 没有读取它的路由规则。
udpbool否false除 TCP 外,还通过 QUIC 数据报中继 UDP。默认关闭,这一点和 socks 入站不同。服务端在每个客户端认证时告知其是否提供 UDP。
udp_idle_timeoutu64否60UDP 关联在两个方向上都持续静默多少秒后被关闭。取值范围为 2 到 600。udp 为 false 时设置它会报错,而不是被忽略。
max_connectionsinteger否4096该监听器同时服务的 QUIC 连接数。超出上限的客户端握手会被立即拒绝。至少为 1。
max_circuitsinteger否65536整个监听器上同时存在的 circuit 数,每个 TCP 流和每个 UDP 关联各算一个。超出上限时,新的流会被重置,新的 UDP 关联会被丢弃。至少为 1。
obfsstring (enum)否—QUIC 之下的数据包混淆。唯一接受的值是 salamander,必须完全一致且小写;其他值会报 unknown obfs。客户端必须使用相同的 obfs 和 obfs_password。
obfs_passwordstring视情况—salamander 的预共享密钥,至少 4 字节。设置了 obfs 时必填;没有 obfs 却设置了它会报错,这样 obfs 拼写错误不会悄悄关闭混淆。
masqueradetable否—对所有不是有效认证的请求(包括凭据错误的请求)返回的 HTTP/3 响应。见下方的 masquerade 字段表。

未知键会被拒绝。例如把键误写成 obfuscation = "salamander",会报 invalid settings: unknown field 并列出可接受的键,而不是启动一个没有混淆的服务端。

一个带两个用户、UDP、混淆、较低上限和 HTML 伪装页的服务端:

server.toml
[[inbound]]
tag = "hy2-in"
protocol = "hysteria2"
listen = "0.0.0.0"
port = 443
[inbound.settings]
cert_file = "/etc/etemenanki/cert.pem"
key_file = "/etc/etemenanki/key.pem"
users = [
{ user = "alice", pass = "replace-with-a-long-random-password", email = "alice@example.com" },
{ user = "bob", pass = "replace-with-another-long-random-password" },
]
udp = true
udp_idle_timeout = 120
max_connections = 2048
max_circuits = 32768
obfs = "salamander"
obfs_password = "replace-with-a-shared-obfs-key"
[inbound.settings.masquerade]
status = 404
body = "<html><body><h1>Not Found</h1></body></html>\n"
content_type = "text/html; charset=utf-8"
[[outbound]]
tag = "direct"
protocol = "freedom"

入站在 listen 和 port 上绑定一个 UDP socket。这带来几点影响:

  • listen 默认为 127.0.0.1,与所有入站相同。需要客户端通过网络访问的服务端,要设置 listen = "0.0.0.0" 或一个明确的地址。
  • 端口是 UDP 端口。 它不会与同一端口号上的 TCP 入站冲突,所以 UDP 443 上的 Hysteria 2 入站可以和 TCP 443 上基于 TLS 的入站同时运行。
  • Unix socket 路径会被拒绝:hysteria2 listens on UDP and cannot use a unix socket。
  • 没有 [inbound.stream]。 QUIC 自带 TLS,证书写在 [inbound.settings] 中。如果 [inbound.stream] 块中的 network 不是 tcp,或 security 不是 none,会报 protocol hysteria2 does not support stream network "…" 或 … stream security "…"。这项检查只看这两个键:[inbound.stream.tls] 表能通过 --test,但不起任何作用,所以请把证书写在 [inbound.settings] 中。

cert_file 和 key_file 必填,没有明文模式。两者都是 PEM 格式;私钥可以是 PKCS#8、PKCS#1(RSA)或 SEC1(EC),也可以和证书放在同一个文件里。监听器只使用 TLS 1.3,并协商 ALPN h3。

证书在构建配置时加载,所以证书有问题时 --test 和重载都会失败:

问题 错误
缺少其中一个键 hysteria2 needs both cert_file and key_file
文件不存在 No such file or directory (os error 2)(消息中不包含文件名)
cert_file 中没有 PEM 证书 hysteria2: the certificate file contains no certificates
key_file 中没有 PEM 私钥 hysteria2: the key file contains no private key
私钥属于另一张证书 hysteria2: certificate and key do not match: …

相对路径相对于进程的工作目录解析,而不是相对于配置文件所在的目录。

password 和 users 必须且只能设置一个。两者都不设置会报 hysteria2 needs password or users,两者都设置会报 password and users cannot both be set; a credential would have two answers。空的 users = [] 视为未设置。

协议只携带一个字符串,即客户端的 auth 值。服务端如何解读它,取决于你设置了哪个键:

入站设置 客户端发送 匹配方式
password = "s3cret" s3cret 整个字符串必须等于 password。
users = [{ user = "alice", pass = "s3cret" }] alice:s3cret 在第一个 : 处拆分。用户名比较时不区分大小写,密码精确比较。

由于在第一个冒号处拆分,用户名不能包含 :,密码则可以。因此 Alice:pa:ss 会以用户 alice、密码 pa:ss 通过认证。users 表的规则在构建配置时检查:

规则 错误
每一项都有 user 和 pass invalid settings: missing field pass …
两者都不为空 hysteria2: a user needs both a name and a password
user 中不含 : hysteria2: a username cannot contain ':' — it separates the two on the wire
转成小写后用户名不重复 hysteria2: two users share a name once lower-cased

共享的 password 同样不能为空:hysteria2: the password must not be empty。

用户名和密码请只使用可打印 ASCII 字符。入站只在 Hysteria-Auth 头仅包含可见 ASCII 字符和空格时才读取它;任何其他字符(例如非 ASCII 字母)都会使凭据被读作空值,因而永远无法匹配。配置检查不会发现这个问题。

每个用户的 email(email 为空时则是 user 名)会作为标签随该用户的流一起传递。etemenanki-app 没有基于它匹配的路由规则。用共享 password 认证的流携带的标签为空。

UDP 默认关闭。当 udp = false 时,服务端在认证时告诉每个客户端它不中继 UDP,并忽略收到的所有数据报。

当 udp = true 时,客户端把每个包作为一个 QUIC 数据报发送,并标上它自己选择的会话 ID。入站为每个会话打开一个 UDP 关联,并对每个包单独路由,所以同一会话的不同包可能走不同的出站,这与其他所有 UDP 入站一致。协议中没有关闭会话的消息;会话在两个方向上都连续 udp_idle_timeout 秒没有包时,由入站关闭,该检查每秒进行一次。

udp_idle_timeout 接受 2 到 600 秒,默认 60。除非 udp = true,否则设置它会被拒绝:

设置 错误
udp_idle_timeout = 1 或 601 udp_idle_timeout must be between 2 and 600 seconds
设置了 udp_idle_timeout,但 udp 为 false 或未设置 udp_idle_timeout is set but udp is not enabled

大于单个 QUIC 数据报的包会被拆成分片,并在另一端重组,两个方向都是如此。重组后的包最大 4096 字节,与上游实现的上限相同。

obfs = "salamander" 会在线路上包装每个 QUIC 包:每个包附加一个随机的 8 字节 salt,并与由 obfs_password 和该 salt 派生出的密钥流做 XOR。没有密钥时,流量看起来就不再像 QUIC。这是混淆而不是加密,内容仍由 QUIC 自身的 TLS 保护。开启 obfs 后,不使用相同密钥的客户端连 QUIC 握手都无法完成,因此也访问不到伪装页。

两端必须使用相同的 obfs_password。入站和出站的检查规则相同,并且都是 fail closed(出错即拒绝),所以拼写错误绝不会导致混淆被关闭:

设置 结果
没有 obfs,也没有 obfs_password 不混淆。
obfs = "salamander",且 obfs_password 不少于 4 字节 使用该密钥进行 Salamander 混淆。
obfs = "salamander",但 obfs_password 更短或缺失 obfs_password must be at least 4 bytes for salamander
有 obfs_password 而没有 obfs obfs_password is set but obfs is not; did you mean obfs = "salamander"?
其他任何 obfs 值,包括 "Salamander" 或 "" unknown obfs "Salamander" (expected "salamander")

使用错误密钥的客户端,或未开启混淆去连接开启了混淆的服务端的客户端,完全得不到回应。它最终以超时失败,而不是认证错误。

伪装响应(masquerade)是入站对所有未成功认证的请求返回的固定 HTTP/3 响应,这些请求包括凭据错误、缺少凭据,或者浏览器请求 /。默认是 Go 的纯文本 404 page not found,与未配置伪装的上游服务端返回的内容相同。

在 [inbound.settings.masquerade] 中设置。未写出的键保持默认值:

键类型必填默认值说明
statusu16否404响应的 HTTP 状态码。接受 100 到 999 之间的任何值,但 233 除外:它是 Hysteria 认证成功的状态码,返回它等于告诉探测者认证已经通过。小于 100 或大于 999 的值会报 is not an HTTP status code。
bodystring否"404 page not found\n"响应体,原样发送,并带上相应的 Content-Length。默认值与 Go 的 http.NotFound 返回的内容逐字节一致。
content_typestring否"text/plain; charset=utf-8"响应头 Content-Type 的值。

上面的服务端示例返回一个状态码为 404 的小 HTML 页面。

status = 233 会被拒绝,报 hysteria2: 233 is the authentication success status and cannot be used for the masquerade:233 是 Hysteria 2 服务端对正确凭据的应答状态码,把它返回给探测者,等于告诉对方认证已经通过。伪装只能是固定响应,没有提供文件或反向代理其他网站的选项。

有两个上限用来保护服务端,两者都必须至少为 1(max_connections must be at least 1、max_circuits must be at least 1):

键 默认值 计数对象 超出上限时
max_connections 4096 该监听器上的 QUIC 连接,无论是否已认证 新连接的握手被拒绝。
max_circuits 65536 该监听器所有连接上的 TCP 流加 UDP 关联 新的流被重置;新 UDP 关联的包被丢弃。

circuit 在整个生命周期内都占用名额,直到中继结束或 UDP 关联超时。另有两个固定上限:每个客户端连接最多同时打开 1024 个流(QUIC 流量控制会让客户端等待更多名额),最多 256 个 UDP 关联。

对每个被代理的 TCP 连接,入站通常先拨号目标,再回应客户端。这样客户端是从一次拒绝得知目标不可达,而不是从一个打开后随即结束的流中得知。

例外是请求中的目标为 IP 地址且 sniffing 开启(默认开启)的情况。客户端在收到应答前不会发送任何数据,所以入站会立即回应“已连接”,从最先收到的字节中(最多 4 KiB,最长 300 ms)读取 TLS 服务器名或 HTTP Host,并用这个域名进行路由。此时拨号失败表现为流被关闭。如果你更想要拒绝应答而不是嗅探到的域名,请在入站上设置 sniffing = false。

来自该入站的流携带入站的 tag,可用于 inbound_tag 规则。source_cidr 规则匹配的是打开该 QUIC 连接的客户端地址,而不是监听器地址,尽管所有客户端共用一个 UDP socket。TCP 流匹配 network = "tcp",中继的 UDP 包匹配 network = "udp"。

出站需要通用的 server 和 port 键:Hysteria 2 服务端的域名或地址,以及它的 UDP 端口。缺少时会报构建错误 missing server 和 missing port。address_family 控制服务端名称的解析方式,名称通过 DNS 中配置的解析器解析。其余设置写在 [outbound.settings] 中:

键类型必填默认值说明
passwordstring是—每个 QUIC 连接发送一次的凭据,放在 Hysteria-Auth 头中。不能为空。服务端使用 users 表时,写成 user:pass。
server_namestring否—TLS 服务器名称(SNI),也是校验证书时使用的名称。默认取出站的 server。当 server 是 IP 地址而证书上写的是域名时,需要设置它。
allow_insecurebool否false接受任何服务端证书,不校验证书链和名称。不能与 ca_file 同时设置。只用于测试。
ca_filepath否—额外 CA 证书的 PEM 文件,这些证书会加入系统信任库,而不是替换它。--test 会读取该文件,但内容要到第一次连接时才解析,所以不含证书的文件会在那时报 hysteria2: the CA file contains no certificates。
obfsstring (enum)否—QUIC 之下的数据包混淆。唯一接受的值是 salamander;其他值会报 unknown obfs。必须与服务端一致。
obfs_passwordstring视情况—salamander 的预共享密钥,至少 4 字节,必须与服务端相同。设置了 obfs 时必填;没有 obfs 却设置它会报错。
max_concurrent_streamsinteger否102400该出站在其唯一的共享连接上可同时打开的 TCP 流数,所有用户共用。超出上限的流会立即失败,报 connection is at its concurrent-stream limit。至少为 1,没有上限。

未知键会被拒绝。没有带宽相关的键:up = "100 mbps" 会报 invalid settings: unknown field up。

client.toml,对应上面的服务端示例
[[outbound]]
tag = "hy2-out"
protocol = "hysteria2"
server = "203.0.113.10"
port = 443
[outbound.settings]
password = "alice:replace-with-a-long-random-password"
server_name = "proxy.example.com" # 证书上的名称
obfs = "salamander"
obfs_password = "replace-with-a-shared-obfs-key"

路由到某个 hysteria2 出站的所有流共享同一个 QUIC 连接。每个 TCP 流在其上打开一个流,每个 UDP 流在其数据报通道上打开一个会话;凭据只在建立连接时发送一次。这是协议本身的工作方式。相比之下,vmess、vless、trojan 和 shadowsocks 出站会在每个流中发送凭据。

  • 连接在第一个流到来时建立,而不是在启动时。如果服务端名称解析出多个地址,会按顺序逐个尝试,每个地址的 QUIC 握手和认证共有 10 秒时间。
  • 连接建立后,每 10 秒发送一次 keep-alive 以保持连接。
  • 连接断开后,下一个流会触发重连。如果连接尝试失败,或上一次连接持续不到 10 秒,出站会等待一段时间再重试:从 2 秒开始,每次翻倍,最多 30 秒。在此期间路由到它的流会立即失败,报 hysteria2: connection is down, waiting before the next attempt。
  • max_concurrent_streams 限制该连接上同时打开的 TCP 流数,所有用户共用。超出的流会立即失败。UDP 会话另有 256 个的上限。

出站用 rustls 根据系统信任库校验服务端证书。校验所用的名称是 server_name,未设置时为 server。该名称是 DNS 名时还会作为 SNI 发送;IP 地址只用于校验,不会发送。

设置 信任 适用场景
什么都不设置 系统 CA 库 公共 CA 签发的证书。
ca_file 系统 CA 库加上 ca_file 中的证书 私有 CA,或按下文方式创建的自签名证书。
allow_insecure = true 任何证书、任何名称 仅用于测试。

allow_insecure 和 ca_file 不能同时设置:allow_insecure and ca_file cannot both be set。TLS 相关的键写在 [outbound.settings] 中,而不是 [outbound.stream.tls],见不支持 stream 块和负载均衡器。

当 server 是 IP 地址时,证书必须列出这个 IP 地址,否则就要把 server_name 设为证书上列出的某个 DNS 名。不然握手会失败,报 certificate not valid for name "203.0.113.10"。

除非设置了 allow_insecure,出站都需要系统信任库,即使设置了 ca_file 也一样。在没有 CA 证书的主机上(例如精简的容器镜像),每次连接都会失败,报 hysteria2: no system root certificates could be loaded;请安装发行版的 CA 证书包(Debian 和 Ubuntu 上是 ca-certificates)。

--test 会读取 ca_file,但不会解析它。不含证书的文件能通过检查,要到第一次连接时才失败,报 hysteria2: the CA file contains no certificates。

出站把 UDP 流作为 QUIC 数据报传输,放不下的包会被分片。它需要服务端中继 UDP:如果服务端表示不中继,路由到该出站的每个 UDP 流都会失败,出站会记录一条警告 hysteria2: the server does not relay UDP; datagrams routed to this outbound are dropped。对于 etemenanki-app 服务端,这意味着服务端入站需要设置 udp = true。

出站自己持有 UDP socket,因此不接受 [outbound.stream] 传输层。network 不是 tcp 或 security 不是 none 时,会报 protocol hysteria2 does not support stream security "tls" 之类的错误。

hysteria2 出站不能作为负载均衡器的成员。负载均衡器通过 TCP 连接检查成员,而 Hysteria 2 服务端只监听 UDP,所以该成员会一直被判定为不可用。这样的配置会被拒绝,报 balancer pool: outbound hy2-out has no upstream a TCP health probe can reach, so it cannot be balanced。

入站可以与上游 Hysteria 2 客户端以及其他遵循协议规范的客户端配合使用。下面的客户端配置对应上文使用 users 表和 Salamander 的入站:

config.yaml(上游客户端)
server: proxy.example.com:443
auth: alice:replace-with-a-long-random-password
obfs:
type: salamander
salamander:
password: replace-with-a-shared-obfs-key
tls:
sni: proxy.example.com
socks5:
listen: 127.0.0.1:1080

对于使用共享 password 的服务端,auth 只填该密码即可。使用自签名测试证书时,在 tls 下加上 insecure: true。

方面 上游 Hysteria 2 etemenanki-app
拥塞控制 设置了 bandwidth 时使用 Brutal(以固定速率发送);否则使用可配置的控制算法,默认 BBR 没有带宽设置。出站把自己的接收速率报告为未知,入站总是回应 auto,两端都使用 quinn 默认的拥塞控制(Cubic)。由于回应的是 auto,上游客户端连接本服务端时会忽略自己的 bandwidth 设置,改用其配置的控制算法而不是 Brutal。
端口跳跃 客户端可以在一个端口范围内跳跃 出站只连接一个 server 和 port。
Fast open 客户端可选 未实现:出站等到服务端应答后才开始中继。
认证 password、userpass、HTTP 和命令后端 password 或 users 表(userpass)。
伪装 提供文件、反向代理网站或固定字符串 固定的 status、body 和 content_type。
QUIC 和 TLS 实现 quic-go 和 Go 的 TLS quinn 和 rustls。

以下值是内置的,不能配置:

项目 值
QUIC 空闲超时(两端) 30 s
客户端 keep-alive 间隔 10 s
流接收窗口 / 连接接收窗口 8 MiB / 20 MiB
每个客户端连接同时打开的流数(入站) 1024
每个连接的 UDP 关联数(两端) 256
重组后最大的 UDP 包 4096 字节
UDP 空闲检查间隔(入站) 1 s
出站每个解析地址的连接尝试 10 s
出站打开一个流 5 s
出站重连退避 2 s,翻倍直至 30 s

限制中的通用上限也同时适用。

对配置文件的任何修改都会重建整个 generation(一代实例),见热重载。对 Hysteria 2 入站来说,这意味着所有 QUIC 连接都会被关闭。旧监听器最多等待几秒,让 UDP 端口释放后再由新监听器绑定,因此新 generation 可以复用同一端口。客户端会自行重连。构建失败的配置会被记录到日志,正在运行的 generation 保持不变。

构建错误,由 --test、启动和重载时报告:

错误 原因和解决办法
hysteria2 needs both cert_file and key_file 入站缺少证书或私钥。没有明文模式。
hysteria2 needs password or users 设置 password,或一张非空的 users 表。
password and users cannot both be set; a credential would have two answers 两者只保留一个。
hysteria2 listens on UDP and cannot use a unix socket listen 是一个路径。请使用 IP 地址。
port is required 入站没有设置 port。
protocol hysteria2 does not support stream network "ws" 删除 [inbound.stream] 或 [outbound.stream] 块。
unknown protocol "Hysteria2" 协议名区分大小写:hysteria2、hysteria 或 hy2。
invalid settings: unknown field … [inbound.settings]、[inbound.settings.masquerade] 或 [outbound.settings] 中有拼错的键。消息中会列出可接受的键。
hysteria2 password must not be empty 出站的 password 为 ""。
max_concurrent_streams must be at least 1 不写该键以使用默认值,或设为正数。

出站的运行时错误,日志形式为 hysteria2: connect failed: hysteria2: no address answered (…),括号内是具体原因:

原因 含义
hysteria2 authentication rejected with status 404 Not Found 服务端不接受该凭据,状态码是其伪装响应的状态码。检查 password;对使用 users 表的服务端,请写成 user:pass。
invalid peer certificate: UnknownIssuer 证书不是由受信任的 CA 签发的。设置 ca_file,或使用公共 CA 签发的证书。
invalid peer certificate: certificate not valid for name "…" server_name(或 server)不在证书上。
invalid peer certificate: Other(OtherError(CaUsedAsEndEntity)) 服务端证书是 CA 证书。用 basicConstraints=critical,CA:FALSE 重新创建。
timed out 10 秒内没有收到 QUIC 应答:UDP 端口被封锁、服务端未运行,或两端的 obfs 设置不一致。