使用手册 Operator's guide
从下载到排错,一口气讲完
From download to
troubleshooting
面向要把 zray 用起来的人:哪个文件、配置怎么写、怎么装成开机自启的服务、出问题去哪儿看。完整的特性与配置参考在 README,链式出站单独有一篇教程。 Which file to download, how to write the config, how to run it as a service that survives a reboot, and where to look when something breaks. The full feature and configuration reference is the README; relay / exit chaining has its own tutorial (Chinese).
- 先确认你能不能用Is this for you?
- 五分钟跑起来(SOCKS5 模式)Running in five minutes (SOCKS5)
- 参数从哪来:分享链接怎么填Where the values come from: share links
- 三种用法Three ways to deploy
- 装成服务,开机自启Run it as a service
- 调优:把有限的 CPU 和内存用对地方Tuning: spend the scarce CPU and RAM well
- 排错Troubleshooting
- 从 v0.3 升级Upgrading from v0.3
- 自带的两个自测工具The two bundled test tools
先确认你能不能用Is this for you?
zray 是一个客户端,服务端不需要它。要跑起来需要三样东西: zray is a client; nothing is deployed on the server side. You need three things:
| 需要Need | 说明Notes |
|---|---|
| 一台 Linux 机器A Linux box | 代理服务只在 Linux 上运行。路由器(OpenWrt / ImmortalWrt)、软路由、树莓派、VPS 都行。macOS / Windows 上只能编译和跑 probe。The proxy service runs on Linux only: a router (OpenWrt / ImmortalWrt), a small x86 or ARM mini-PC, a Raspberry Pi, a VPS. On macOS / Windows you can build it and run probe, nothing else. |
| 一个服务端节点A node to connect to | REALITY(VLESS + xtls-rprx-vision),或者普通 VLESS + TLS(机场常见的那种)。协议必须是 VLESS,Trojan / VMess / Shadowsocks 都不支持。REALITY (VLESS + xtls-rprx-vision), or plain VLESS + TLS (what most commercial providers offer). VLESS only — Trojan / VMess / Shadowsocks are not supported. |
| 系统的出口方式A way to route traffic in | 你的手机、电脑、整栋房子要么手动填 SOCKS5,要么靠透明代理自动接管。Either you point clients at its SOCKS5 port by hand, or a transparent nft rule hands it the traffic. |
如果你只有一台线路好、IP 也干净的服务器,直连就够了,不需要链式那一套。见链式出站教程开头的判断。 If you have one server with both a good route and a clean IP, use it directly; the chaining machinery buys you nothing. See the decision section at the top of the chaining tutorial.
它不是什么:不是图形客户端,没有订阅、没有自动测速、没有负载均衡、没有日志级别开关(每条连接的生命周期都打到 stderr)。这些不在设计目标里。 What it is not: not a GUI client, no subscriptions, no automatic latency testing, no load balancing, no log level switch (every connection logs its lifecycle to stderr). Those are outside the design goals.
五分钟跑起来(SOCKS5 模式)Running in five minutes (SOCKS5)
选对二进制Pick the right binary
每个 tag 的 Release 里都有五个静态 musl 二进制,没有动态依赖,也不用装任何库: Every tagged release carries five static musl binaries. No shared libraries, nothing to install:
| 文件File | 适用设备Target |
|---|---|
zray-mipsel-softfloat | 小端 MIPS,绝大多数 MT7621 / MT7620 路由器(小米 AC2100、红米 AC2100、新路由 3 等)Little-endian MIPS — most MT7621 / MT7620 routers (Xiaomi AC2100, Redmi AC2100, Newifi 3, ...) |
zray-mips-softfloat | 大端 MIPS,少数老设备Big-endian MIPS, a few older boxes |
zray-arm-v7 | ARMv7,如多数高通 / 博通 A7 路由器、树莓派 2/3ARMv7: most Qualcomm / Broadcom A7 routers, Raspberry Pi 2/3 |
zray-aarch64 | ARM64,如树莓派 4、ARM 软路由、ARM VPSARM64: Raspberry Pi 4, ARM mini-PCs, ARM VPS |
zray-x86_64 | 普通 PC / x86 软路由 / x86 VPSAny PC / x86 mini-PC / x86 VPS |
uname -m 在 MIPS 上只回 mips,分不出大小端。在 OpenWrt 上用发行版架构串最准:
uname -m prints mips for both MIPS endiannesses, so it cannot tell you which one you have. On OpenWrt ask the distro instead:
. /etc/openwrt_release && echo $DISTRIB_ARCH
# mipsel_24kc / mips_24kc / aarch64_generic / arm_cortex-a7_neon-vfpv4 / x86_64
mipsel 开头 → zray-mipsel-softfloat,mips_ 开头 → zray-mips-softfloat,其余按上表对。还拿不准就再跑一次 opkg print-architecture,mipsel_ 前缀即小端。
mipsel* → zray-mipsel-softfloat, mips_* → zray-mips-softfloat, everything else maps by the table. opkg print-architecture prints the same string.
下载、校验、放到固定位置Download, verify, put it somewhere permanent
mkdir -p /mnt/zray
cd /mnt/zray
wget -O zray https://github.com/yunnysunny/zray/releases/latest/download/zray-mipsel-softfloat
wget -O zray.sha256 https://github.com/yunnysunny/zray/releases/latest/download/zray-mipsel-softfloat.sha256
chmod 755 zray
sha256sum zray # 和 zray.sha256 里的第一列目视比对 / compare with the first column
./zray # 没有配置文件时报 cannot read config.json,说明二进制能跑
除了一个配置文件路径之外,zray 不接受任何参数:没有 -h、没有 --version,zray --help 会被当成文件名去读。版本号看启动日志第一行。不要放在 /tmp(重启就没了);/mnt/zray 这类数据分区在 OpenWrt 上重启后仍在。
Apart from a config path, zray takes no arguments: no -h, no --version. zray --help is treated as a file name. The version is the first field of the first startup log line. Do not leave it in /tmp (wiped on reboot); /mnt/zray on OpenWrt survives reboots.
写一份最小配置Write a minimal config
{
"listen": "127.0.0.1:1080",
"server": "服务器地址 / host or IP",
"port": 443,
"sni": "伪装域名 / camouflage site",
"security": "reality",
"public_key": "服务端 publicKey / server publicKey",
"short_id": "服务端 shortId,没有就留空 / empty if none",
"uuid": "你的 uuid / your uuid",
"max_connections": 64
}
其余字段都有默认值,见 config.example.json。两条硬性规则: Everything else has a default; see config.example.json. Two hard rules:
- 配置里出现不认识的键会直接报错退出(
bad config: UnknownField)。多写一个逗号、键名拼错,都不会被静默忽略。An unknown key is a startup error (bad config: UnknownField). A trailing comma or a misspelled key is never silently ignored. security是显式声明的。"reality"必须给public_key;"tls"不许出现public_key/short_id。写错组合就报错,不会悄悄降级成不校验证书的 TLS。securityis explicit."reality"requirespublic_key;"tls"rejectspublic_key/short_id. A wrong combination fails at startup instead of quietly downgrading to unverified TLS.
实用一下的话,把 max_connections 按内存算(每个槽约 93 KiB,见第 6 节)。
Size max_connections from the memory you have (about 93 KiB per slot, see section 06).
前台跑一次,看着日志Run it in the foreground and read the log
cd /mnt/zray && ./zray config.json
# 正常启动会打两行 / a healthy start prints two lines:
zray 0.4.0 socks5 [127.0.0.1:1080] redirect [] sniff true, 1 outbounds, 0 rules, 64 slots x 93 KiB = 5937 KiB
outbound -: server:443 sni camouflage mode reality (short_id -) vision
mode reality 就是 REALITY 模式;如果是 mode tls, verify NONE -- WARNING: ...,说明你在连普通 VLESS+TLS 节点,证书不校验,链路上的任何人都有可能冒充它。
mode reality means REALITY. mode tls, verify NONE -- WARNING: ... means you are on a plain VLESS+TLS node with no certificate verification: anyone on the path can impersonate it.
验证真的通了Prove it actually works
curl --socks5-hostname 127.0.0.1:1080 https://www.cloudflare.com/cdn-cgi/trace
# ip= 应为服务器出口 IP,loc= 是国家地区码
curl --socks5-hostname 127.0.0.1:1080 https://ipinfo.io/json
# 第二条验证 DNS 也走隧道 / the second check proves DNS goes through too
两条都通,这台机器上的 SOCKS5 模式就配好了。前端接上它就行:SwitchyOmega、sing-box 的 socks outbound、手机上任意支持 SOCKS5 的客户端,地址填 路由器IP:1080。
Once both work, point a client at it: SwitchyOmega, a sing-box socks outbound, any phone SOCKS5 client — address router-ip:1080.
SOCKS5 入口没有认证,也没有绑定检查之外的保护。listen 保持 127.0.0.1;确实要给局域网用时改 0.0.0.0:1080,但要清楚同一网段的任何人都能拿它当出口。
The SOCKS5 inbound has no authentication. Keep listen on 127.0.0.1; if you must expose it to the LAN, 0.0.0.0:1080 works, but anyone on that network can use it as an exit.
参数从哪来:分享链接怎么填Where the values come from: share links
机场和自建服务一般给一条 vless:// 链接,字段和 zray 配置一一对应:
Providers and self-hosted panels hand out a vless:// link. Its fields map one-to-one onto the config:
vless://<uuid>@<地址 host>:<端口 port>?encryption=none&security=reality&sni=example.com&fp=chrome&pbk=<公钥 public key>&sid=ab12&type=tcp&flow=xtls-rprx-vision#名字
| 链接里的In the link | 配置字段Config key | 备注Notes |
|---|---|---|
uuid | uuid | |
@ 后面的主机 / 端口host / port after @ | server / port | 主机可以是域名,也可直接写 IPhostname or literal IP |
security=reality | "security": "reality" | |
security=tls | "security": "tls" + "tls_verify": "none" | 后者在当前版本必填the latter is mandatory in this version |
pbk= | public_key | base64url,原样抄base64url, copy verbatim |
sid= | short_id | 十六进制,最多 8 字节;没有就留空hex, up to 8 bytes; empty if absent |
sni= | sni | REALITY 的伪装站点,必须和服务端一致REALITY camouflage site, must match the server |
fp=chrome | — | 指纹固定是 Chrome,不需要也不可能改成别的the fingerprint is always Chrome; it cannot be changed |
flow=xtls-rprx-vision | flow | tcp 下默认就是 vision,可以省略the default on tcp, so it can be omitted |
encryption=none | — | VLESS 固定 noneVLESS is always none |
type=tcp | — | 默认就是 tcpthe default |
type=ws | "network": "ws" | 配合 path= → ws_path,host= → ws_hostwith path= → ws_path, host= → ws_host |
不支持 / 需要注意: Not supported / watch out for:
type=grpc、type=httpupgrade、type=quic:都没有实现,只能用tcp和ws。type=grpc,type=httpupgrade,type=quic: not implemented.tcpandwsonly.- ws 的 early data(链接里带
ed=2048这类参数):不实现。别把它塞进ws_path,服务端真发 early data 时连接会以WsEarlyData断开。大多数服务端在没有客户端请求时不会主动发。WebSocket early data (ed=2048and friends): not implemented. Do not put it intows_path; if the server sends early data alongside the 101, the connection dies withWsEarlyData. fp不是 chrome(如firefox、safari、randomized):客户端只呈现 Chrome 指纹,写了也没用。服务端若按指纹做了限制会连不上。fpother than chrome: the client only presents a Chrome fingerprint; setting something else does nothing, and a server that gates on fingerprint will refuse the connection.- 订阅 / 订阅转换:zray 不读订阅链接,需要自己把节点展开成链接再抄进配置。Subscriptions: zray does not read them. Expand the node into its fields yourself.
- 免费或公共节点常见的「同名多落地、自动选最快」:不属于 zray 的能力范围,它的
rules只按域名后缀分流,不做测速。"Same name, many landings, pick the fastest": out of scope.rulesonly splits by domain suffix, there is no latency testing.
三种用法Three ways to deploy
本机 / 局域网 SOCKS5(最简单)Local / LAN SOCKS5 (simplest)
就是第 02 节那套。适合:单台机器或几台设备手动挂代理;先验证节点可用,再折腾透明代理。 What section 02 sets up. Good for one machine or a handful of devices, and for validating a node before touching transparent proxying.
UDP 走 SOCKS5 UDP ASSOCIATE,用 XUDP(VLESS mux)封装。注意两点:只支持带 Vision 的 TCP 节点(ws 节点没有 UDP),载荷超过约 600 字节可能被服务端出口丢弃(DNS 正常,QUIC 一般不通)。分片不支持,现实中也没有客户端用。
UDP rides SOCKS5 UDP ASSOCIATE, wrapped in XUDP (VLESS mux). Two limits: Vision TCP outbounds only (a ws outbound carries no UDP), and payloads above roughly 600 bytes may be dropped by the server's outbound (DNS works, QUIC generally does not). Fragmentation is not supported; no real client uses it.
透明代理(路由器当网关)Transparent proxy (the router is the gateway)
目标:局域网里的设备什么都不用配,命中规则的流量自动进隧道。 Goal: LAN devices need no configuration, matching traffic enters the tunnel by itself.
1. 配置两个入口。listen 留着当调试用,redirect 才是给 nft 喂流量的:
1. Configure two inbounds. Keep listen for debugging; redirect is what nft feeds:
{
"listen": "127.0.0.1:10800",
"redirect": "0.0.0.0:1080",
"sniff": true,
"server": "服务器地址 / host or IP",
"port": 2443,
"bootstrap_dns": "223.5.5.5",
"...": "其余同前 / the rest as before"
}
2. nft 规则把命中的 IP 重定向到 redirect 端口。典型的按域名分流:DNS 侧(dnsmasq + chinadns-ng,或 smartdns)把命中 gfwlist 的域名解析出的 IP 写进一个 nft 集合,再由这张表负责转发:
2. Redirect matching destinations to the redirect port. A typical domain-based split: the DNS side (dnsmasq + chinadns-ng, or smartdns) writes the IPs of matched domains into an nft set, and this table forwards them:
table inet gfw {
set iplist { type ipv4_addr; flags interval; }
chain prerouting { type nat hook prerouting priority 0;
ip daddr @iplist ip protocol tcp redirect to :1080 }
chain output { type nat hook output priority filter;
ip daddr @iplist ip protocol tcp redirect to :1080 }
}
prerouting 管其他设备,output 管路由器自己。redirect 用的是 SO_ORIGINAL_DST,所以端口写的是 zray 的 redirect 端口(上例 1080),不是 listen。
prerouting covers other devices, output covers the router itself. redirect relies on SO_ORIGINAL_DST, so the port here is zray's redirect port (1080 above), never listen.
3. 两条必须避开的坑: 3. Two traps that must be avoided:
- 服务器自己的 IP 不能进那个集合。否则 zray 连服务器的流量又被自己拦回来,变成死循环(症状:启动正常、一有流量就满槽位、日志里全是失败)。用
ip daddr != <服务器 IP>排除,或者在 DNS 侧把服务器域名加进直连名单。The server's own IP must never land in that set. Otherwise zray's connection to its server is redirected back into zray — a loop whose symptom is a healthy start, then instant slot exhaustion and a log full of failures. Exclude it withip daddr != <server IP>, or put the server hostname on the direct list. - 解析
server的解析器不能依赖这条隧道。如果 dnsmasq / chinadns-ng 的 DoT 上游本身走 zray,开机时两边互相等,谁都起不来。这时把bootstrap_dns设成公共 DNS(如223.5.5.5),启动时那次解析就完全不经过本地解析器。Whatever resolvesservermust not depend on the tunnel. If dnsmasq / chinadns-ng forwards DoT through zray, both wait for each other at boot and neither starts. Setbootstrap_dnsto a public resolver (e.g.223.5.5.5) so the startup lookup bypasses the local resolver entirely.
4. sniff 保持开启。透明代理只拿得到目标 IP,sniff 让它从首包解出 TLS SNI 或 HTTP Host,把目标换成域名。目标 IP 有可能命中 rules 时,zray 最多等 1 秒做这次嗅探——这是那 1 秒的来源,正常。
4. Leave sniff on. A transparent inbound only sees the destination IP; sniff extracts the TLS SNI or HTTP Host from the first client bytes and swaps the IP for the name. When the target IP could match a rules entry, zray waits up to one second for that sniff — that second is expected.
5. 验证:在局域网另一台机器上不配代理,直接: 5. Verify from another LAN machine with no proxy configured:
curl https://www.cloudflare.com/cdn-cgi/trace # ip= 应为服务器出口 IP
curl https://www.baidu.com # 应正常(不在集合里,直连)
多节点、按域名分流、链式出站Several nodes, domain routing, chaining
需要「线路好的机器」和「IP 干净的机器」各一台时用这个:rules 把少数域名分给一组 exit,其余走 default。
For when "a machine with a good route" and "a machine with a clean IP" are different boxes: rules send a few domains to a group of exits, everything else stays on default.
{
"outbounds": [
{ "tag": "relay", "server": "...", "security": "reality", "public_key": "...", "uuid": "..." },
{ "tag": "exit-a", "server": "...", "security": "tls", "tls_verify": "none",
"uuid": "...", "network": "ws", "ws_path": "/vless-ws", "flow": "none", "dialer": "relay" }
],
"rules": [
{ "domain_suffix": ["openai.com", "chatgpt.com", "claude.ai"], "outbound": ["exit-a"] }
],
"default": ["relay"]
}
规则要点:outbound 是一个组,组内成员之间才做故障切换(fail_backoff_s,默认 30 秒);命中的规则失败后不会退回 default,这是故意的——宁可打不开,也不让本该走干净 IP 的请求从别处出去。链式的完整原理、封装层次、代价和验证方法都在链式出站教程里。
Rules worth knowing: outbound is a group, and failover (fail_backoff_s, 30 s by default) only rotates inside that group; a matched rule never falls back to default. That is deliberate — better a failed request than a request that was supposed to leave from the clean IP going out somewhere else. The full mechanics live in the chaining tutorial (Chinese).
装成服务,开机自启Run it as a service
OpenWrt:用仓库里的 procd 脚本OpenWrt: the procd script in the repo
scp zray root@router:/mnt/zray/zray
scp config.json root@router:/mnt/zray/config.json
scp deploy/zray.init root@router:/etc/init.d/zray
ssh root@router '
chmod 755 /etc/init.d/zray /mnt/zray/zray
chmod 600 /mnt/zray/config.json
/etc/init.d/zray enable
/etc/init.d/zray start
sleep 2; logread | tail -5
'
deploy/zray.init 只做三件事:procd_set_param command 指向 /mnt/zray/zray /mnt/zray/config.json、respawn 3600 5 0(崩了自动拉起,5 秒内连崩就停)、把 stdout / stderr 交给 logd。想看实时日志:logread -f。配置文件的权限用 600(里面有 uuid 和 public_key),二进制用 755。别把配置放在 web 目录下。
deploy/zray.init does three things: procd_set_param command runs /mnt/zray/zray /mnt/zray/config.json, respawn 3600 5 0 restarts it after a crash (and gives up if it crashes five times within 5 s), and stdout / stderr are handed to logd. Live log: logread -f. Keep the config at mode 600 (it holds the uuid and public_key) and the binary at 755.
普通 Linux:systemdPlain Linux: systemd
[Unit]
Description=zray REALITY/VLESS client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/opt/zray/zray /etc/zray/config.json
Restart=always
RestartSec=3
LimitNOFILE=1024
# 透明代理入口监听 1024 以下端口时用 / if the redirect inbound uses a privileged port:
# AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now zray
journalctl -u zray -f
仓库没有附带 systemd 单元,上面这份可以直接抄。透明代理的 nft / iptables 规则要另外持久化(nft -f /etc/nftables.conf 或发行版自己的机制),zray 不负责装规则。
The repo does not ship a unit file; the one above works as-is. Transparent-proxy nft / iptables rules are yours to persist; zray does not install them.
调优:把有限的 CPU 和内存用对地方Tuning: spend the scarce CPU and RAM well
内存:max_connections × 93 KiBMemory: max_connections × 93 KiB
启动日志最后一段就是账:64 slots x 93 KiB = 5937 KiB。槽位在启动时一次性分配,不随流量增长,超出就拒绝新连接(conn table full)。
The tail of the startup line is the ledger: 64 slots x 93 KiB = 5937 KiB. Slots are allocated once at startup and do not grow with traffic; when they run out, new connections are refused (conn table full).
| 场景Scenario | 估算Estimate |
|---|---|
| 普通 SOCKS5 / 透明代理Plain SOCKS5 / transparent | 并发连接数 × 93 KiBconcurrent connections × 93 KiB |
链式(dialer)Chained (dialer) | 再乘 2:每条链式连接占两个槽(客户端那条 + hop 那条)× 2: each chained connection holds two slots (client side + hop) |
| 128 MB 内存的路由器(可用约 20 MB)128 MB router (~20 MB free) | 32–64 个槽比较合适32–64 slots is a sensible range |
64 槽 ≈ 5.8 MB,128 槽 ≈ 11.6 MB。先给 64,出现 conn table full 再往上加;一加就 OOM 就说明该换更大的机器了。
64 slots ≈ 5.8 MB, 128 slots ≈ 11.6 MB. Start at 64; raise it when you see conn table full, and stop when raising it triggers the OOM killer.
TCP 窗口:tcp_rmem(比任何 zray 参数都重要)TCP window: tcp_rmem (matters more than any zray option)
128 MB 内存的路由器上,net.ipv4.tcp_rmem 默认上限约 930 KB,单条流的吞吐被「窗口 / RTT」卡住:190 ms 时约 1 MB/s。在这类机器上实测,把上限提到 4 MB 之后同一条链路的 HTTPS 吞吐从约 1 MB/s 变成 9–10 MB/s:
On a 128 MB router the default net.ipv4.tcp_rmem ceiling is about 930 KB, capping one stream at roughly window / RTT — about 1 MB/s at 190 ms. On the reference box, raising the ceiling to 4 MB lifted HTTPS through the tunnel from ~1 MB/s to 9–10 MB/s:
echo 'net.ipv4.tcp_rmem = 4096 131072 4194304' > /etc/sysctl.d/99-zray.conf
sysctl -p /etc/sysctl.d/99-zray.conf
不要用 zray 的 sock_buf 来干这件事:它会关掉内核的窗口自动调优,还会被 net.core.rmem_max 截断。sock_buf 留 0。
Do not reach for zray's sock_buf here: it disables the kernel's window autotuning and is clamped by net.core.rmem_max. Leave sock_buf at 0.
超时Timeouts
| 字段Key | 默认Default | 什么时候改When to change it |
|---|---|---|
connect_timeout_s | 5 | 跨境 + ws 握手慢的节点偶发超时,调到 8–10raise to 8–10 if a slow cross-border + ws handshake occasionally times out |
handshake_timeout_s | 15 | 入站侧:等客户端把 SOCKS5 握手 / 首个请求送出来。移动网络下可放宽inbound side: waiting for the client to finish SOCKS5 and send a request; relax on mobile networks |
idle_timeout_s | 600 | 手机推送这类长连接会被它掐断,可调到 1800cuts long-lived connections such as phone push; raise to 1800 if pushes stall |
connect_timeout_s 必须 ≥ 1,设 0 直接报错退出。
connect_timeout_s must be ≥ 1; 0 is a startup error.
别给自己找麻烦的几条Things not to do
- Vision 节点才是默认选择。
flow: "none"或 ws 节点整条连接都要软件加解密,MIPS 单核上限约 1–1.5 MB/s。Prefer Vision nodes. Aflow: "none"or ws node keeps the outer TLS for the whole connection, and a single MIPS core tops out around 1–1.5 MB/s. default放在 Vision 节点上,只有需要干净 IP 的少数域名走 ws exit。Keepdefaulton a Vision node; send only the few domains that need a clean IP to a ws exit.- 别把 REALITY 服务端域名加进 gfwlist(见第 04 节的坑)。Keep the REALITY server's domain out of gfwlist (section 04, trap 3).
排错Troubleshooting
日志全部打到 stderr,OpenWrt 上在 logread,systemd 上在 journalctl。按症状查:
Everything goes to stderr — logread on OpenWrt, journalctl on systemd. Find your symptom:
| 症状Symptom | 原因Cause | 处理Fix |
|---|---|---|
bad config: UnknownField | 配置里有关键名拼错,或多了不该有的字段a misspelled or extra key | 对照 config.example.json 逐字核对;"..." 这种占位键也是未知键compare against config.example.json character by character; a "..." placeholder is an unknown key too |
bad config: use either top-level server/uuid or outbounds, not both | 顶层 server / uuid 和 outbounds 同时存在both forms present | 二选一pick one |
bad config: connect_timeout_s must be >= 1 | 设成了 0set to 0 | 设 ≥ 1set ≥ 1 |
cannot resolve <host>: ... | 启动时解析上游失败startup resolution failed | 检查网络;解析器依赖隧道时设 bootstrap_dns;它只查 A 记录,只有 AAAA 的域名请改用系统解析器check the network; set bootstrap_dns when the local resolver depends on the tunnel; it only asks for A records, so an AAAA-only host needs the system resolver |
CertificateRejected | 路由器时钟没同步(REALITY 会校验封在 session_id 里的时间戳);或者 public_key / short_id / uuid 不匹配router clock not synced (REALITY checks the timestamp sealed in session_id), or a key mismatch | 先对时:ntpd -q -p ntp.aliyun.com,确认 date 正确;再核对参数sync first (ntpd -q -p pool.ntp.org, check date), then re-check the parameters |
conn table full | 槽位用尽:并发太多,或上游不可达导致每条连接都占着坑等超时slots exhausted: too much concurrency, or an unreachable upstream holding a slot per connection until the timeout | 增大 max_connections;同时查上游可达性(不可达时会连锁占坑)raise max_connections; check upstream reachability |
| 启动正常,一有流量就全是失败、槽位很快满healthy start, then instant failures and exhausted slots | 透明代理把服务器自己的流量也 redirect 回来了the transparent inbound redirects the connection to the server itself | 把服务器 IP 排除出 nft 集合exclude the server IP from the nft set |
| 透明代理完全没效果transparent proxy does nothing | nft 规则没命中 / 端口写成了 listen / sniff 关了 / 客户端没把网关和 DNS 指到路由器rule not matching / port set to listen / sniff off / client gateway+DNS not pointed at the router | 逐项核对第 04 节walk through section 04 |
| 能打开 IP 站点,域名打不开IP-only sites work, domains do not | DNS 没走对,或 sniff 关闭导致域名匹配不上DNS not routed correctly, or sniff off so names never reach the rules | 检查 DNS 分流;sniff 保持 truefix DNS splitting; keep sniff: true |
| ws 节点特别慢(< 1 MB/s)ws node is slow (< 1 MB/s) | ws 不能用 Vision,整条连接软件加解密,是 CPU 而不是线路ws cannot use Vision; the whole connection is software-encrypted — it is CPU, not the line | 正常现象,只用它跑 AI 聊天这类小流量expected; use it for chat-sized traffic only |
链式失败日志 ... -> relay sidechain failure ... -> relay side | 路由器到 relay 没连上(或 relay 的下一段握手没完成)the router never got through to the relay (or the relay's own handshake did not finish) | 查 relay 本身、家宽出口check the relay and your home uplink |
链式失败日志 ... -> exit sidechain failure ... -> exit side | relay 通了,exit 没响应或握手出错the relay is up, the exit never replied or the handshake failed | 查 exit 地址 / 端口 / uuid / ws_path,以及 relay 出站是否被 exit 白名单拦了check exit address / port / uuid / ws_path, and whether the relay's egress is allowed by the exit |
链式失败日志 hop connection never acceptedchain failure hop connection never accepted | relay 侧没有接受本机 hop 的 SOCKS 连接the relay side never accepted the local hop SOCKS connection | 通常与 dialer 指错节点有关usually a dialer pointing at the wrong node |
WsUpgradeRejected | WS 升级返回了非 101(多半是 404)the upgrade returned something other than 101 (usually 404) | ws_path / ws_host 和服务端不一致ws_path / ws_host do not match the server |
WsEarlyData | 服务端在同一记录里发了 101 之后的早期数据the server sent early data in the same record as the 101 | 关掉服务端的 early data;别把 ed= 写进 ws_pathturn early data off on the server; keep ed= out of ws_path |
tls alert handshake_failure / CertificateRejected | sni 与服务端证书或 REALITY 配置不符sni does not match the server certificate or REALITY config | 核对 snire-check sni |
| UDP 不通、QUIC 打不开no UDP, QUIC fails | 只有 Vision TCP 节点带 UDP,且载荷 > ~600 字节可能被服务端丢only Vision TCP outbounds carry UDP, and payloads > ~600 bytes may be dropped by the server | 属于已知限制,浏览器自动回落 TCPknown limit; browsers fall back to TCP |
| 手机上开着代理却推送延迟 / 断流phone push stalls or drops | idle_timeout_s 掐掉了长连接idle_timeout_s closes long-lived connections | 调大 idle_timeout_sraise idle_timeout_s |
判断「到底是谁的问题」的通用方法:先用第 09 节的 probe 直接对服务端做一次握手和请求。probe 通、zray 不通,问题在 zray 的配置或入口;probe 也不通,问题在服务端或线路上。
A general way to tell whose problem it is: run probe (section 09) straight at the server. probe works but zray does not → the problem is zray's config or inbound. Neither works → the problem is the server or the path.
从 v0.3 升级Upgrading from v0.3
0.5.0 在同一监听端口支持 SOCKS5 与 HTTP 代理,可通过 workers 配置网络工作线程,并提供可选的内核 AES-GCM 加密后端和动态 libc 链接构建。同时改进了 Vision 转发,并修复 TLS 密钥更新、连接关闭和旧内核随机数获取等兼容性问题。升级只需要换二进制:
0.5.0 supports SOCKS5 and HTTP proxy traffic on the same listener, configurable network workers via workers, optional kernel AES-GCM backends and dynamic libc builds. It also improves Vision forwarding and fixes TLS key updates, connection shutdown and random-number generation on older kernels. Upgrading is a binary swap:
/etc/init.d/zray stop
# 覆盖 /mnt/zray/zray,保留原配置 / overwrite the binary, keep the config
/etc/init.d/zray start
配置向后兼容:只写了顶层 server / uuid 的旧配置照常工作,connect_timeout_s 默认 5 秒。要留意的是:
Configs stay backward compatible: a top-level server / uuid config keeps working, connect_timeout_s defaults to 5 s. Two things to keep in mind:
- 开始用链式时,
max_connections要按连接数翻倍往上加(每条链占两个槽)。Once you start chaining, raisemax_connectionsto twice the connection count (two slots per chained connection). - 0.4 之前没有
connect_timeout_s,入站侧的超时由handshake_timeout_s兜着;现在它只管入站,上游有独立的 5 秒硬超时。Before 0.4,handshake_timeout_scovered the upstream attempts too; now it is inbound-only and upstreams have their own 5 s deadline.
自带的两个自测工具The two bundled test tools
probe:直接验服务端probe: check a server directly
不启动服务,做一次真实握手 + 一次经隧道的请求,任何平台都能编译运行(包括 Windows / macOS): Does one real handshake and one proxied request without starting the service. Builds and runs anywhere, Windows and macOS included:
zig build probe -- <ip> <port> <sni> <public_key> [short_id|-] [uuid url]
zig build probe -- 203.0.113.5 443 example.com K5Ko4T...4G8 - \
00000000-0000-0000-0000-000000000000 https://www.cloudflare.com/
输出包含服务端选的 cipher suite 和密钥交换组、证书 HMAC 是否通过、经隧道收到的字节数。把 URL 换成 https://tls.peet.ws/api/all 还能看到内层连接呈现的指纹。
The output shows the cipher suite and key-exchange group the REALITY server chose, whether the certificate HMAC verified, and the bytes received through the tunnel. Point the URL at https://tls.peet.ws/api/all to see the fingerprint the inner connection presents.
udptest.py:验 UDPudptest.py: check UDP
python3 tools/udptest.py 127.0.0.1 1080
用 SOCKS5 UDP ASSOCIATE 发几次 DNS 查询,验证 XUDP 通路。需要 Python 3,没有第三方依赖。
Sends a few DNS queries over SOCKS5 UDP ASSOCIATE to exercise the XUDP path. Needs Python 3, no third-party packages.
反馈Feedback
配置被拒时,报错信息里带着键名和原因;调试时把启动那两行日志和报错一起贴出来,比只说「连不上」有用得多。问题与想法:GitHub Issues。这份手册的 Markdown 版在 docs/usage.zh-CN.md 与 docs/usage.md。 Config errors name the offending key and the reason; when asking for help, paste the two startup log lines together with the error — far more useful than "it does not connect". Issues: GitHub Issues. The Markdown source of this guide lives at docs/usage.md and docs/usage.zh-CN.md.