范围与脱敏说明
本文记录一次真实的软路由代理接入过程。最终目标是:
``text
国内流量
→ 软路由直
境外流量 → PassWall → Hysteria 2 → VPS 出口 ```
为避免把基础设施凭证公开到 Git 仓库,文中的公网 IP、订阅域名、认证密码、混淆密码和证书指纹均使用占位符:
VPS_IP
SUB_DOMAIN
AUTH_PASSWORD
OBFS_PASSWORD
CERT_PIN_SHA256实际运行时必须替换为自己的值。任何包含 hysteria2:// 完整链接、认证密码、混淆密码或证书指纹的文件都不应提交到公开仓库。
环境基线
本次环境如下:
``text
VPS
Ubuntu 24.04
x86-64 / amd64
Hysteria 2 server
UDP 20000-20100
Salamander obfuscation
self-signed TLS certificat
软路由 LibWrt SNAPSHOT qualcommax/ipq60xx aarch64_cortex-a53 uname -m = aarch64 opkg PassWall 25.6.28-r1
客户端 iPhone / Shadowrocket Windows / Hiddify Mac / Shadowrocket 或其他兼容客户端 ```
软路由最终安装的 Hysteria 版本为:
Version: v2.11.0
Platform: linux
Architecture: arm64最终结果
最终验收通过:
``text
Hysteria 2 核心
/usr/bin/hysteria
可在 ARM64 软路由正常运
独立客户端测试 成功连接 VPS 出口 IP 等于 VPS 公网 IP
PassWall TCP 节点:Tokyo-HY2-Hop UDP 节点:与 TCP 节点相同 中国列表:直连 其余 TCP/UDP:代理
透明代理 Hysteria 子进程正常存活 PassWall 防火墙规则正常加载 国内网站直连 境外网站通过 VPS ```
问题的最终根因不是 Hysteria 服务端,也不是 UDP 端口跳跃,而是 PassWall 保存的证书指纹与 VPS 当前叶子证书不一致:
FATAL failed to initialize client
connect error: INTERNAL_ERROR (local):
no certificate matches the pinned hash同步真实证书 SHA-256 指纹后,代理恢复正常。
架构选择
WireGuard 作为备用
最初使用原生 WireGuard。它的吞吐和延迟表现较好,但固定 UDP 端口在当前网络路径下偶发不可用。
WireGuard 没有被删除,而是保留为应急备用。
Tailscale Exit Node 作为第二备用
Tailscale Exit Node 能稳定工作,但相较原生 WireGuard:
延迟更高
抖动更明显
吞吐略低
链路多一层控制面与中继可能性因此保留为低维护备用,不作为主要出口。
Hysteria 2 作为主线路
最终选择 Hysteria 2,原因是:
基于 QUIC / UDP
支持端口跳跃
支持 Salamander 混淆
移动网络兼容性较好
同一个可执行文件包含服务端与客户端
可由 PassWall 直接接管透明代理本次采用的端口范围:
20000-20100/udp客户端每 30 秒切换端口。
VPS 端配置模板
下面是可复现的通用模板。真实环境中的密码必须使用随机值,并通过安全渠道保存。
安装 Hysteria 2 服务端
官方安装脚本会安装二进制文件和 systemd 服务:
bash <(curl -fsSL https://get.hy2.sh/)确认:
hysteria version
systemctl status hysteria-server.service生成自签名证书
本文使用 hy2.local 作为 SNI:
``bash
install -d -m 0755 /etc/hysteri
openssl req \ -x509 \ -newkey rsa:2048 \ -nodes \ -sha256 \ -days 3650 \ -keyout /etc/hysteria/server.key \ -out /etc/hysteria/server.crt \ -subj '/CN=hy2.local' \ -addext 'subjectAltName=DNS:hy2.local'
chmod 0600 /etc/hysteria/server.key chmod 0644 /etc/hysteria/server.crt ```
查看叶子证书 SHA-256 指纹:
openssl x509 \
-noout \
-fingerprint \
-sha256 \
-in /etc/hysteria/server.crt输出格式类似:
sha256 Fingerprint=AA:BB:CC:...服务端配置
/etc/hysteria/config.yaml:
``yaml
listen: :20000-2010
tls: cert: /etc/hysteria/server.crt key: /etc/hysteria/server.key
auth: type: password password: AUTH_PASSWORD
obfs: type: salamander salamander: password: OBFS_PASSWORD ```
重启并查看日志:
``bash
systemctl enable --now hysteria-server.service
systemctl restart hysteria-server.servic
systemctl status hysteria-server.service journalctl --no-pager -e -u hysteria-server.service ```
云安全组必须允许:
UDP 20000-20100若系统本地启用了 UFW,可额外放行:
ufw allow 20000:20100/udp不要在没有确认当前防火墙实现的情况下混用 UFW、iptables 和 nftables。
先做独立客户端验证
在接入 PassWall 之前,应先证明以下链路成立:
软路由 Hysteria 二进制
→ Hysteria 服务端
→ VPS 出口如果独立客户端都无法连接,就不应继续调试 PassWall、DNS 或透明代理。
通用客户端配置:
``yaml
server: "VPS_IP:20000-20100"
auth: "AUTH_PASSWORD
tls: sni: "hy2.local" insecure: true pinSHA256: "CERT_PIN_SHA256"
obfs: type: salamander salamander: password: "OBFS_PASSWORD"
transport: type: udp udp: hopInterval: 30s
http: listen: 127.0.0.1:18080 ```
启动:
/usr/bin/hysteria client -c /root/hy2-client.yaml另开终端测试:
``sh
http_proxy="http://127.0.0.1:18080" \
wget -qO- http://api.ipify.or
echo ```
成功时输出 VPS 公网 IP。
用 VPS 中转 ARM64 核心
软路由无法稳定访问软件源和 GitHub,因此不再强行让 opkg 下载 Hysteria,而是:
VPS 下载官方 ARM64 二进制
→ 软路由通过 SSH 从 VPS 读取
→ 在软路由本地安装最终正确版脚本
下面脚本在软路由上运行。
它不会让 amd64 VPS 执行 ARM64 文件,只在 VPS 上下载和计算哈希,然后把文件传给软路由。
``sh
cat >/tmp/install-hysteria-from-vps.sh <<'SCRIPT'
#!/bin/sh
set -
VPS_IP="VPS_IP" REMOTE_FILE="/root/hysteria-linux-arm64" LOCAL_TMP="/tmp/hysteria-linux-arm64" LOCAL_BIN="/usr/bin/hysteria"
fail() { echo "失败:$*" >&2 return 1 }
echo "[1/5] 检查软路由架构" ARCH="$(uname -m)" echo "uname -m: $ARCH"
case "$ARCH" in aarch64|arm64) ;; *) fail "当前设备不是 ARM64,不能安装 hysteria-linux-arm64" exit 1 ;; esac
echo echo "[2/5] 让 VPS 下载官方 ARM64 文件" echo "需要输入 VPS root 密码"
ssh "root@${VPS_IP}" " set -e curl -fL \ https://download.hysteria.network/app/latest/hysteria-linux-arm64 \ -o '${REMOTE_FILE}' chmod 0755 '${REMOTE_FILE}' sha256sum '${REMOTE_FILE}' " || { fail "VPS 下载失败" exit 1 }
echo echo "[3/5] 从 VPS 传到软路由"
ssh "root@${VPS_IP}" \ "cat '${REMOTE_FILE}'" \ > "$LOCAL_TMP" || { rm -f "$LOCAL_TMP" fail "传输失败" exit 1 }
chmod 0755 "$LOCAL_TMP"
echo echo "[4/5] 在 ARM64 软路由执行版本检查"
"$LOCAL_TMP" version || { rm -f "$LOCAL_TMP" fail "二进制无法在当前软路由执行" exit 1 }
echo echo "[5/5] 安装"
install -m 0755 "$LOCAL_TMP" "$LOCAL_BIN" || { fail "安装失败" exit 1 }
rm -f "$LOCAL_TMP"
echo echo "安装完成" ls -l "$LOCAL_BIN" "$LOCAL_BIN" version SCRIPT
chmod 0700 /tmp/install-hysteria-from-vps.sh sh /tmp/install-hysteria-from-vps.sh ```
为什么不用随机 IPK
在 Snapshot 固件上安装来源不明或版本不匹配的 IPK,可能造成:
ABI 不兼容
依赖不完整
包数据库污染
LuCI 与核心版本错配
系统升级困难单文件 Hysteria 官方 ARM64 二进制更容易验证、回滚和替换。
PassWall 节点配置
本次 PassWall 版本为:
25.6.28-r1节点字段:
``text
节点备注
Tokyo-HY2-Ho
类型 Hysteria2
协议 UDP
地址 VPS_IP
端口 20000
端口跳跃范围 20001-20100
端口跳跃时间 30s
认证密码 AUTH_PASSWORD
混淆密码 OBFS_PASSWORD
域名 / SNI hy2.local
允许不安全连接 开启
PinSHA256 CERT_PIN_SHA256
快速打开 关闭
延迟启动 关闭
禁用 MTU 检测 关闭
最大上行 / 最大下行 留空
QUIC 接收窗口 保持默认 ```
这里的主端口与跳跃范围会合并为:
VPS_IP:20000,20001-20100基本设置
``text
主开关
开
TCP 节点 Tokyo-HY2-Hop
UDP 节点 与 TCP 节点相同
中国列表 直连
TCP 代理模式 代理
UDP 代理模式 代理
直连列表 开启
代理列表 开启
GFW 列表 开启
IPv6 代理 初期关闭 ```
DNS 在首次接入时尽量保持 PassWall 默认流程,不要同时修改系统 DHCP、dnsmasq、WAN DNS 和客户端手动 DNS,否则会增加变量数量。
让 PassWall 找到手动安装的核心
手动复制 /usr/bin/hysteria 后,PassWall 不一定自动识别路径。
对 PassWall 25.6.28,可明确设置:
``sh
uci set passwall.@global_app[0].hysteria_file='/usr/bin/hysteria'
uci commit passwal
/etc/init.d/passwall restart ```
验证:
uci -q get passwall.@global_app[0].hysteria_file应输出:
/usr/bin/hysteriaPassWall 正确的诊断入口
不要只查 `logread`
本次最初执行:
logread | grep -iE 'passwall|hysteria|hy2'只看到 ucitrack,没有实际启动错误。
PassWall 25.6.28 的主要日志在:
/tmp/log/passwall.log应查看:
tail -n 150 /tmp/log/passwall.log进程检查
ps w | grep '[h]ysteria' || echo "没有 Hysteria 进程"监听端口不要猜
最初误以为 SOCKS 端口一定是 1070,但本次透明代理实际使用:
TCP REDIRECT: 1041
UDP TPROXY: 1041应以 PassWall 日志为准:
grep -E '监听端口|REDIRECT|TPROXY' /tmp/log/passwall.log直接运行 PassWall 生成的配置
当 PassWall 显示“运行完成”,但 Hysteria 进程不存在时,应直接运行它生成的 JSON,以取得核心的真实错误:
``sh
CFG="
for F in $(find /tmp/etc/passwall -type f -name '*.json' 2>/dev/null); do if grep -q '"server"' "$F" 2>/dev/null && grep -q '"auth"' "$F" 2>/dev/null; then CFG="$F" break fi done
echo "配置:${CFG:-未找到}"
if [ -n "$CFG" ]; then /usr/bin/hysteria \ -c "$CFG" \ client \ >/tmp/hy2-passwall-direct.log 2>&1 &
PID=$! sleep 5
if kill -0 "$PID" 2>/dev/null; then echo "进程正常存活" kill "$PID" wait "$PID" 2>/dev/null else echo "启动后立即退出" fi
sed -n '1,160p' /tmp/hy2-passwall-direct.log fi ```
本次就是通过这一步得到关键错误:
no certificate matches the pinned hash一次性同步真实证书指纹
证书指纹必须来自 VPS 当前实际使用的叶子证书,而不是旧 URI、旧截图、旧订阅或旧参数文件。
下面脚本:
备份 PassWall 配置
从 VPS 读取当前证书
计算 SHA-256 指纹
更新当前 Hysteria2 节点
固定核心路径
重启 PassWall
自动检查进程与出口脚本在软路由运行。
``sh
cat >/tmp/fix-passwall-hy2-pin.sh <<'SCRIPT'
#!/bin/s
VPS_IP="VPS_IP" CERT="/etc/hysteria/server.crt" CORE="/usr/bin/hysteria"
fail() { echo echo "修复失败:$*" echo "未修改 WAN、LAN、无线或 DHCP。" exit 1 }
echo "[1/6] 检查节点和核心"
NODE="$(uci -q get passwall.@global[0].tcp_node)" [ -n "$NODE" ] || fail "未找到 PassWall TCP 节点"
NODE_TYPE="$(uci -q get "passwall.${NODE}.type")" [ "$NODE_TYPE" = "Hysteria2" ] || fail "当前 TCP 节点不是 Hysteria2:${NODE_TYPE:-未知}"
[ -x "$CORE" ] || fail "$CORE 不存在或没有执行权限"
echo "节点:$NODE" echo "核心:$CORE"
echo echo "[2/6] 备份 PassWall 配置"
TS="$(date +%Y%m%d-%H%M%S)" BACKUP="/root/passwall-before-pin-fix-${TS}.conf"
cp -a /etc/config/passwall "$BACKUP" || fail "无法备份 /etc/config/passwall"
echo "备份:$BACKUP"
echo echo "[3/6] 从 VPS 获取当前叶子证书指纹" echo "需要输入一次 VPS root 密码"
RAW="$( ssh -o ConnectTimeout=15 "root@${VPS_IP}" \ "openssl x509 -noout -fingerprint -sha256 -in '${CERT}'" )" SSH_RC=$?
[ "$SSH_RC" -eq 0 ] || fail "无法从 VPS 获取证书指纹"
PIN="$( printf '%s\n' "$RAW" | sed -n 's/^[^=]*=//p' | head -n 1 | tr -d '\r\n ' )"
HEX="$(printf '%s' "$PIN" | tr -d ':')"
[ "${#HEX}" -eq 64 ] || fail "证书指纹长度异常"
if printf '%s' "$HEX" | grep -q '[^0-9A-Fa-f]'; then fail "证书指纹格式异常" fi
echo "指纹格式验证通过,不在终端显示。"
echo echo "[4/6] 更新 PassWall 节点"
uci set "passwall.${NODE}.hysteria2_tls_pinSHA256=${PIN}" uci set "passwall.${NODE}.tls_serverName=hy2.local" uci set "passwall.${NODE}.tls_allowInsecure=1"
uci set "passwall.${NODE}.address=${VPS_IP}" uci set "passwall.${NODE}.port=20000" uci set "passwall.${NODE}.protocol=udp" uci set "passwall.${NODE}.hysteria2_hop=20001-20100" uci set "passwall.${NODE}.hysteria2_hop_interval=30s"
uci set "passwall.@global_app[0].hysteria_file=${CORE}"
uci commit passwall || fail "保存 PassWall 配置失败"
echo echo "[5/6] 重启 PassWall"
rm -f /tmp/log/passwall.log /etc/init.d/passwall restart sleep 12
echo echo "[6/6] 验收"
if ps w | grep -q '[h]ysteria'; then echo "Hysteria 进程:已启动" else echo "Hysteria 进程:未启动" echo tail -n 120 /tmp/log/passwall.log 2>/dev/null fail "Hysteria 子进程未存活" fi
OUT="$( wget -qO- --timeout=20 https://api.ipify.org 2>/dev/null )"
if [ "$OUT" = "$VPS_IP" ]; then echo echo "修复成功" echo "Hysteria 进程:正常" echo "代理出口:$OUT" else echo echo "出口测试返回:${OUT:-空}" echo tail -n 120 /tmp/log/passwall.log 2>/dev/null fail "出口验收未通过" fi SCRIPT
chmod 0700 /tmp/fix-passwall-hy2-pin.sh sh /tmp/fix-passwall-hy2-pin.sh ```
PassWall 版本差异
本文针对:
luci-app-passwall 25.6.28-r1该版本使用:
hysteria2_tls_pinSHA256
hysteria2_hop
hysteria2_hop_interval
hysteria_file新版本 PassWall 的内部字段可能不同。升级后应先查看对应版本源码或 LuCI 保存结果,不要直接复制旧版 UCI 字段。
安全回滚
PassWall 配置失败时,不应立刻恢复出厂,也不应重启整个网络服务。
先关闭 PassWall:
/etc/init.d/passwall stop
/etc/init.d/firewall restart
/etc/init.d/dnsmasq restart这组命令的目标是:
停止代理进程
清除代理防火墙状态
恢复 DNS 服务
保留 WAN / LAN / Wi-Fi 配置不要优先执行:
/etc/init.d/network restart网络整体重启可能断开当前管理连接,使排障难度进一步增加。
若要恢复 PassWall 配置:
``sh
cp -a \
/root/passwall-before-pin-fix-YYYYMMDD-HHMMSS.conf \
/etc/config/passwal
/etc/init.d/passwall restart ```
本次踩过的坑
坑 1:在 amd64 VPS 上执行 ARM64 二进制
错误代码:
/root/hysteria-linux-arm64 versionVPS 是 amd64,因此返回:
cannot execute binary file: Exec format error正确思路是:
VPS 只负责下载和中转
ARM64 文件只在 ARM64 软路由执行检查架构:
uname -m
file /path/to/binary坑 2:`sed` 替换字符串中的 `&`
错误修复命令试图把一行改成:
sha256sum /root/hysteria-linux-arm64 >&2但在 sed replacement 中,& 表示“整个匹配内容”。未转义时,最终生成了错误重定向,出现:
22: Bad file descriptor安全做法:
sed -i '/需要删除的完整模式/d' file或在 replacement 中转义:
\&自动修改 shell 脚本后,必须先查看目标行再执行:
sed -n '1,80p' script.sh坑 3:BusyBox 命令能力与桌面 Linux 不同
软路由上的 BusyBox 没有:
timeoutnc 也只支持:
nc IP PORT不支持:
nc -w 10 IP PORT因此不能假设 OpenWrt/LibWrt 具备 GNU coreutils 和完整版 netcat。
运行前先检查:
command -v timeout
nc --help
busybox坑 4:手机热点不等于自动共享代理
iPhone 连接 Shadowrocket 后开启个人热点,并不代表热点客户端自动继承 Shadowrocket 隧道。
还需要 Shadowrocket 的显式“代理共享 / 局域网共享”。
本次软路由能 Ping 通:
172.20.10.1但访问共享端口返回:
Connection refused这说明:
热点链路正常
手机网关正常
目标 TCP 端口没有监听最终放弃手机反向代理,改用 VPS 中转,路径更短、变量更少。
坑 5:恢复出厂会清空配置,但不一定符合预期
软路由恢复出厂后:
自定义 WAN / LAN 配置可能丢失
Wi-Fi SSID 与密码恢复默认
后装包可能被清除或禁用
Web 终端插件可能消失
SSH 主机密钥可能变化恢复后应先建立最小基线:
设置管理密码
确认 WAN 获取地址
确认 LAN 管理可用
确认国内网站可访问
再安装代理核心不要在基础网络未验收前叠加 PassWall、DNS、WireGuard 或手机热点接口。
坑 6:地址输入错误
节点地址曾被手动输入为错误 IP。
排障时先检查最基础字段:
地址
端口
认证
SNI
混淆
证书指纹不要直接跳到复杂 DNS 或 nftables。
坑 7:PassWall 显示“运行完成”不等于核心正常
PassWall 主日志显示:
防火墙规则加载完成
运行完成但 Hysteria 子进程实际上已经退出。
因此必须同时检查:
ps w | grep '[h]ysteria'
tail -n 120 /tmp/log/passwall.log必要时直接运行生成的 JSON。
坑 8:查错日志位置
logread 没有显示核心错误,不代表没有错误。
本次真正有用的日志是:
/tmp/log/passwall.log
/tmp/hy2-passwall-direct.log坑 9:误把 1070 当成固定监听端口
1070 是 PassWall 某些 SOCKS 场景的默认端口,不代表透明代理一定监听它。
本次实际是:
1041应以当前日志和配置为准。
坑 10:手动安装核心后没有绑定 PassWall 路径
独立运行成功,但 PassWall 没有启动核心,原因之一是:
passwall.@global_app[0].hysteria_file未指向 /usr/bin/hysteria。
手动安装二进制后,应用层仍需要知道它的位置。
坑 11:证书指纹过期或来源错误
旧 URI 或旧参数文件中的指纹,与 VPS 当前证书不同。
Hysteria 2.6.4 之后,pinSHA256 只匹配叶子证书指纹。证书更新后必须同步新指纹。
正确来源:
openssl x509 \
-noout \
-fingerprint \
-sha256 \
-in /etc/hysteria/server.crt不要从截图手抄,不要使用 CA 指纹,不要沿用已经更换证书前的值。
坑 12:粘贴脚本中的 `exit` 会关闭当前 SSH
在交互式 SSH 会话中直接粘贴:
[ condition ] || {
echo "错误"
exit 1
}会退出当前 shell,并关闭 SSH。
长脚本应写入文件后执行:
``sh
cat >/tmp/task.sh <<'SCRIPT'
#!/bin/sh
...
SCRIP
sh /tmp/task.sh ```
这样 exit 只退出子脚本,不会直接关闭父级 SSH 会话。
坑 13:截图会泄露节点密钥
PassWall 节点编辑页可能直接显示:
混淆密码
认证密码
证书指纹公开截图前必须打码。完整 hysteria2:// 链接应视为凭证。
最终验收清单
软路由基础网络
ip -4 route
ping -c 3 223.5.5.5
nslookup www.baidu.com 127.0.0.1核心
ls -l /usr/bin/hysteria
/usr/bin/hysteria versionPassWall 路径
uci -q get passwall.@global_app[0].hysteria_file进程
ps w | grep '[h]ysteria'日志
tail -n 150 /tmp/log/passwall.log境外出口
wget -qO- --timeout=20 https://api.ipify.org
echo应返回 VPS 公网 IP。
国内直连
从接入软路由的客户端测试:
百度正常
国内 DNS 正常
国内延迟不经过 VPS境外代理
Google / YouTube 正常
出口为 VPS
客户端自身的 VPN / Hiddify / Shadowrocket 已关闭客户端自身代理必须关闭,否则无法判断流量究竟经过软路由还是本机代理。
运维建议
证书更新后同步指纹
每次更换 /etc/hysteria/server.crt 后执行一次指纹同步脚本。
更新核心仍通过 VPS 中转
在软路由无法稳定访问官方源时,重复使用:
VPS 下载
→ SSH 传输
→ 本机 version 验证
→ install 覆盖
→ 重启 PassWall覆盖前备份:
cp -a \
/usr/bin/hysteria \
"/root/hysteria.backup.$(date +%Y%m%d-%H%M%S)"保留备用链路
建议保留:
主线路:Hysteria 2
备用一:WireGuard
备用二:Tailscale Exit Node但不要同时让多套透明代理接管默认路由和 DNS。
不执行全量 `opkg upgrade`
Snapshot 固件不应无差别执行:
opkg upgrade包和内核模块版本错配可能导致系统不可启动或网络模块失效。
先验证一层,再进入下一层
推荐顺序:
WAN / LAN
→ VPS 服务端
→ 软路由独立 Hysteria 客户端
→ PassWall 核心路径
→ PassWall 节点
→ 透明代理
→ DNS 分流
→ 客户端验收每一层通过后再继续。不要一次修改网络、无线、DHCP、DNS、代理和防火墙。
参考资料
- Hysteria 2 安装:<https://v2.hysteria.network/docs/getting-started/Installation/>
- Hysteria 2 客户端与证书固定:<https://v2.hysteria.network/docs/getting-started/Client/>
- Hysteria 2 完整客户端配置:<https://v2.hysteria.network/docs/advanced/Full-Client-Config/>
- Hysteria 2 端口跳跃:<https://v2.hysteria.network/docs/advanced/Port-Hopping/>
- Hysteria 2 Changelog:<https://v2.hysteria.network/docs/Changelog/>
- PassWall 25.6.28 Hysteria2 配置生成器:<https://github.com/Openwrt-Passwall/openwrt-passwall/blob/25.6.28-1/luci-app-passwall/luasrc/passwall/util_hysteria2.lua>
- PassWall 25.6.28 Hysteria2 LuCI 字段:<https://github.com/Openwrt-Passwall/openwrt-passwall/blob/25.6.28-1/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua>