我想解决的事情其实很简单:人在外面,也能打开家里的 NAS。
但我不太想为了这件事,再给电脑、手机和平板分别配置一套 SSH 连接,更不想把 SSH 私钥到处复制。
既然这些设备平时已经在用 Clash,背后也有一台自己的科学上网 VPS,那就换个思路:让这台 VPS 顺便帮我访问家里的设备。
手机和电脑继续用原来的节点。新增的工作放到 VPS 上,客户端只需要告诉 Clash:访问家里这些 IP 时,也交给这台 VPS。
先说清楚:不是让手机直接 SSH 回家
假设家里的 NAS 地址是 192.168.123.10,管理页面用的是 5000 端口。
在家里,我可以打开:
1 | http://192.168.123.10:5000/ |
到了外面,这个地址并不会自动指向我家里的 NAS。现在要做的,就是给这次访问安排一条能回家的路:
1 | 手机 / 电脑上的 Clash |
Clash 和 sshuttle 不在同一台机器上。Clash 在手机、电脑上;sshuttle 在那台科学上网 VPS 上。
这里没有新增一个“SSH 节点”,也没有额外开一个 SOCKS5 服务。客户端仍然使用原来的 VLESS Reality 等代理节点;只是 VPS 收到访问家里 IP 的请求后,再由 sshuttle 把连接送回家。1
可以把它理解为:以前 VPS 只帮我访问外网,现在多接了一份“访问家里设备”的活。
开始前,先确认这几件事
本文不是从零搭建科学上网服务。它假设你已经有一台自己能管理的 Linux VPS,Clash 也已经能通过这台服务器正常上网。
另外,VPS 必须能通过 SSH 连上一台“能访问家里内网”的设备。这台设备可以是 NAS、Linux 主机,或者具备相应环境的路由器;它需要能运行兼容的 Python。2
如果 VPS 连家里的 SSH 都连不上,就先解决这一步。sshuttle 不能在没有任何可用连接的情况下,凭空打通家里的网络。
本文还默认 Xray 和 sshuttle 直接运行在同一台 VPS 的系统里。如果 Xray 装在 Docker 容器中,先留意文末的容器提醒,不要把“同一台服务器”当成“网络一定一样”。
下面统一使用这些示例值:
| 示例 | 代表什么 | 需要改吗 |
|---|---|---|
VPS-Reality |
Clash 里已有的科学上网节点名 | 改成你的节点名 |
home.example.com |
VPS 能连接到的家庭 SSH 地址 | 改成你的域名或 IP |
tunneluser |
家里 SSH 设备上的登录用户名 | 改成你的用户名 |
58022 |
VPS 连接家庭 SSH 时使用的端口 | 按实际填写 |
192.168.123.0/24 |
想访问的家庭网段 | 按实际填写 |
192.168.123.10:5000 |
用来测试的 NAS 地址和端口 | 换成确实开放的服务 |
其中,192.168.123.0/24 可以先简单理解成“192.168.123.* 这一组地址”。不是让你把家里的设备都改成这些 IP,而是把示例换成自己的地址。
第一步:在 VPS 上准备 sshuttle
这一节的命令,都在科学上网 VPS 上执行,不是在手机、Mac,也不是在家里的 NAS 上执行。
Ubuntu / Debian 可以这样安装:
1 | sudo apt update |
记下 command -v sshuttle 输出的路径。下面按 /usr/bin/sshuttle 写;如果你得到的是 /usr/local/bin/sshuttle,后面的配置也跟着改。
接着,试着从 VPS 登录家里的 SSH:
1 | sudo -H ssh -p 58022 [email protected] |
第一次连接时,先通过可信渠道核对主机指纹,再接受并保存。登录成功后输入 exit,回到 VPS。
这里特意使用 sudo -H,是为了和后面后台服务使用的 root 身份保持一致。普通用户能登录,不代表 root 也已经有同样的 SSH 密钥。
后台运行之前,先把免密登录确认好
执行下面这条测试:
1 | sudo -H ssh -o BatchMode=yes \ |
正常情况下,它会输出 SSH_OK 和 Python 版本,然后退出。
如果出现 Permission denied,说明 SSH 认证还没准备好。先配置 VPS 到家庭设备的公钥登录,再继续;不要指望后台服务替你输入密码。带口令的私钥也需要另外安排后台解锁,不能只靠终端里临时运行的 SSH agent。3
私钥留在 VPS;家庭设备保存对应的公钥。手机、电脑不需要拿到这份 SSH 私钥。
如果提示 /opt/bin/python3 不存在,就检查家里那台 SSH 设备的 Python 路径。后面出现的 --python /opt/bin/python3,指的也是家里设备上的 Python,不是 VPS 上的。4
版本要求要与实际安装的 sshuttle 对应。例如 sshuttle 2.0.0 的官方要求是两端 Python 均不低于 3.10,不要只确认“装了 Python”就跳过检查。2
第二步:让 VPS 自动连回家,断了再重连
接下来要解决的不是“手动连一次”,而是“VPS 重启后自己连接,断线后自己再试”。
这件事交给 systemd。你可以把它理解成 Linux 里负责启动、停止和照看后台程序的工具,不用先把所有参数背下来。5
仍然在 VPS 上创建文件:
1 | sudo nano /etc/systemd/system/sshuttle.service |
写入:
1 | [Unit] |
这段配置不用逐字研究,先抓住它做的事情:
连接谁? [email protected]:58022,也就是家里的 SSH 设备。
帮忙访问哪些地址? 只处理 192.168.123.0/24。这里不要为了省事写成 0.0.0.0/0,否则就变成了让 sshuttle 接管 VPS 的全部 IPv4 目标流量,不再是“只访问家里”。4
断了怎么办? SSH 长时间收不到回复时,会通过心跳判断失联;进程退出后,systemd 等 10 秒再启动。这里的 30 秒和 3 次,对应典型情况下约 90 秒发现失联,不是保证 90 秒就一定恢复。3 5
为什么不一直问密码? BatchMode=yes 让认证失败直接报错,而不是等人输入。accept-new 会记录新主机的密钥、拒绝已记录密钥的异常变化,但首次连接的指纹仍需自己核实。3
StartLimitIntervalSec=0 是为了持续重试,不因短时间内多次启动失败而停止。network-online.target 只帮助安排开机时的启动顺序,并不会全天替你检查网络。6 7
保存以后执行:
1 | sudo systemd-analyze verify /etc/systemd/system/sshuttle.service |
如果你是在修改一份已经运行的服务,再执行一次:
1 | sudo systemctl restart sshuttle.service |
需要查看发生了什么,就看日志:
1 | sudo journalctl -u sshuttle.service -n 50 --no-pager |
先别急着改 Clash,先让 VPS 自己试一遍
在 VPS 上执行:
1 | curl --noproxy '*' -v --connect-timeout 5 --max-time 15 \ |
这里的 --noproxy '*' 是让 curl 不使用环境变量里配置的 HTTP/SOCKS 代理,直接测试 VPS 发起的连接;sshuttle 仍然可以接管它。8
能收到 NAS 返回的页面、跳转或 HTTP 响应,就说明这段路至少已经走通:
1 | VPS → sshuttle → 家里的 SSH 设备 → NAS |
如果这里失败,先不要动 Clash。问题还在 VPS 到家里这一段,优先检查 SSH 登录、Python、目标服务和 sshuttle 日志。
另外,服务显示 active (running),只代表程序在运行,不能替代上面的访问测试。5
第三步:告诉 Clash,访问家里也用这个节点
现在回到手机或电脑上的 Clash / FlClash。
假设你原来的科学上网节点名叫 VPS-Reality,而 sshuttle 正好就装在这台节点对应的 VPS 上。
找到配置里已有的 rules:,在它最前面插入这一行。不要再新建第二个 rules:,也不要删除原有的其他规则。
1 | rules: |
翻译成一句话就是:
访问
192.168.123.*时,交给VPS-Reality;其他地址继续按下面原有的规则处理。
IP-CIDR 表示按 IP 网段判断;no-resolve 表示不要为了这条 IP 规则额外解析域名。这里访问的是明确的 IP 地址,不需要另外配置家庭域名解析。9
记得把 Clash 设为规则模式。规则写对了,但运行在全局或直连模式下,就不是按这套分流逻辑处理。10
为什么要放前面,为什么不能写 DIRECT?
因为不少配置会把内网地址直接设为 DIRECT,也就是让当前这台手机或电脑自己连接。
人在外面时,让手机自己访问 192.168.123.10,并不能找到家里的 NAS。我们需要的是:先交给 VPS,再让 VPS 帮忙回家。
Mihomo 的普通规则从上往下匹配,所以这条“回家规则”要放在宽泛的私网直连规则之前。9
还有一点:别把目标写成会在不同服务器之间切换的“自动选择”组。只有安装了 sshuttle、能连回家的那台 VPS 才认识这条路。内网访问应固定走它,日常外网仍然可以继续用原来的自动选择。
保存、重新加载配置后,打开:
1 | http://192.168.123.10:5000/ |
到这里,客户端侧的核心配置就完成了。没有新增 SSH 节点,也没有给手机分发 SSH 私钥。
不过,是否能打开还取决于两个地方:这条访问有没有真的进入 Clash,以及 VPS 上的代理服务有没有允许访问内网。下面专门说这两件事。
打不开时,别把所有配置一起改
先记住一个排查顺序:VPS 自己能不能访问 → Clash 有没有收到 → 有没有选对节点 → 服务端有没有拦住。
Clash 的连接列表里,根本没有这条访问
这时先别怀疑 sshuttle,Clash 可能还没接到活。
使用系统代理时,检查应用或系统是否设置了“局域网地址不使用代理”。使用 TUN 时,检查配置里是否有:
1 | tun: |
这条配置把整个 192.168.* 都排除在 TUN 自动接管范围之外,自然也包括家里的 192.168.123.*。11
Clash 没收到连接,后面的分流规则写得再正确也没用。
需要让目标网段不再被排除,同时保留其他必要的绕过设置。文末给了“其他 192.168.* 仍然绕过,只有 192.168.123.* 进入 TUN”的现成配置。
连接列表里能看到,但显示 DIRECT 或其他节点
检查这条规则有没有真正加入当前生效的配置、是否排在私网直连规则前面,以及节点名是否写对。
你想看到的是:192.168.123.10:5000 交给了运行 sshuttle 的那台 VPS,而不是交给当前网络,也不是另一台代理服务器。
如果配置来自订阅,也要留意更新后自定义规则是否还在。以客户端实际生效的配置为准,不要只看之前保存的文件。
节点没选错,VPS 自己也能访问,但手机还是打不开
接下来检查 VPS 上的 Xray。
VPS 能访问家里,不代表它允许所有代理客户端也访问家里。 这是两回事。
例如,服务端配置可能把私网地址拦住;当前 Xray 官方文档也说明,Freedom 出站对 VLESS 等入站默认有私有和保留地址访问限制,需要按需明确放行。12
不要因此把所有保护都关闭。只允许需要访问的设备和端口就好,例如 NAS 的 192.168.123.10:5000。文末有对应示例,但必须先确认你的 Xray 版本支持相关字段。
电脑上还有一个方便的分辨方法
假设本机 Clash 的 HTTP 或 mixed 代理端口是 7890,可以在电脑上执行:
1 | curl --noproxy '' -v --connect-timeout 5 --max-time 15 \ |
这个命令是明确要求 curl 把访问交给 Clash;--noproxy '' 则避免环境变量里的“不走代理名单”把测试绕过去。端口不同就改成实际值。8
如果这样能通,而浏览器直接访问不通,优先看浏览器、系统代理和 TUN 设置。如果这样也不通,再看节点选择和 VPS 端的权限、日志。
有几个边界,提前知道能少绕路
别用 ping 作为唯一的判断标准。 本文用的是 TCP 服务测试。sshuttle 常用的 NAT / nft 方式不提供完整的 ICMP、普通 UDP 和广播转发,所以“网页能开但 ping 不通”不矛盾,也别期待 NAS 一定自动出现在局域网设备列表里。2
外面的网络最好不要和家里用同一段地址。 两边都叫 192.168.123.* 时,同一个 IP 到底指向哪边就容易混淆。优先把其中一边换成不同网段,尤其不要直接照搬 TUN 配置去覆盖当前网络的网关地址。
Xray 装在 Docker 里要单独检查。 Docker bridge 模式可以理解为“容器自己还有一层网络”,不是宿主机上测试成功,容器就一定成功。本文没有配置容器流量接入 sshuttle;这种部署需另外验证和调整。不要为了试错就清空防火墙,或直接改动正在使用的容器网络模式。13
SSH 私钥少放几份,是方便管理,不是从此没有风险。 一旦某人拿到有内网权限的代理账户,他仍可能通过 VPS 访问已放行的设备。只放行必要的地址和端口,NAS 自身也要保留可靠的登录认证。VPS 是这条路的中间站,同样需要保护。12
还有,自动重连只负责重新建立通路,不保证原来的下载、网页会话不断。连接中断后,应用可能仍需刷新或重试。
最后,把它记成一句话就够了
客户端还是原来的 Clash,节点还是原来的 VPS;只是在 VPS 上加一个 sshuttle,让它多一条回家的路。
访问外网,照旧。访问家里的地址,先交给指定 VPS,再由它送回家。
我更喜欢这种分工:需要长期保持的 SSH 连接集中在 VPS,手机和电脑不再各自维护一套。换设备时,也不用再琢磨 SSH 私钥该怎么复制、放在哪里。
不是又搭了一套更复杂的系统,而是让已经在用的那台服务器,多做一件有用的事。
备用配置:用到再看
下面是上文两个特殊情况的完整示例。已经能够正常访问的,不必为了“配置更全”继续修改。
TUN:只让家里的 192.168.123.* 不被排除
下面这些网段加起来,恰好是 192.168.0.0/16 去掉 192.168.123.0/24。不必手算,按自己的目标网段确认后使用即可。
在已有的 tun: 中合并相关设置,不要创建第二个 tun::
1 | tun: |
原来的 192.168.0.0/16 排除项必须去掉,否则仍然把目标网段排除了。原有其他确实需要的排除项可以保留,但不能再有覆盖目标网段的更大排除范围。
这里修改的是“哪些连接进入 TUN”,不是代替前面的 IP-CIDR 规则。实际效果还受系统路由、平台及客户端版本影响;保存后仍然要看连接是否进入 Clash。11
Xray:只放行指定的 NAS 地址和端口
先确认实际 Xray 版本,以及负责连接目标地址的出站配置。以下是支持 finalRules 的 Xray 的示例,不适合不加检查地复制到所有版本或其他代理程序中。12
在已有的、实际用于该连接的 freedom 出站中合并:
1 | { |
意思是:允许访问 192.168.123.10 的 TCP 5000、5001 端口。不是允许所有人登录 NAS,也不是自动替 NAS 打开了这些服务;NAS 上对应服务必须本来就在运行。12
这是一个出站配置片段,不是完整 Xray 配置。保留已有设置和安全规则;如果已有 finalRules,把这条例外放在会拦截它的规则之前,不要覆盖整个列表。
还要检查服务端原有的 routing.rules。如果连接在这里就被私网拦截规则送进了 blackhole,它根本走不到上面的 freedom,所以只改 finalRules 仍然不够。14
这种情况下,需要把以下精确例外加在那条私网拦截规则之前:
1 | { |
这里的 direct 必须与刚才那个 Freedom 出站的真实 tag 一致。这是 VPS 服务端的出站标签,不是让你把 Clash 的内网规则改成 DIRECT。14
这些示例按目标地址和端口放行,并未区分代理用户。多人共用节点时,仍需另外设计用户权限;不要以为“只开放两个端口”就等于“只有自己能访问”。
通过面板管理 Xray 的,使用面板支持的配置入口,并确认最终生成的配置确实生效。修改前备份,按对应版本校验配置后再重载;无法确认版本和规则含义时,不要整段替换线上配置。
参考文档
本文地址、用户名、端口和节点名均为示例;安装路径、Python 要求和服务端策略以实际版本为准。以下文档用于核对参数含义,不代表上面的示例已在每一种系统与客户端组合中实测。
1. sshuttle 工作原理 · 2. sshuttle 系统要求 · 3. OpenSSH 客户端配置 · 4. sshuttle 参数手册
5. systemd 服务配置 · 6. systemd 单元配置 · 7. 网络启动顺序 · 8. curl 手册