反向隧道:让云上的服务器读到你本地的文件
我平时在微信上跟一个 AI 助手打交道,它跑在一台云服务器上。我在公司的 Windows 电脑上做嵌入式开发,时不时想让它看一眼本地的东西——某个工程目录、一份文档、一段编译日志。
我最早问它「你能读我电脑上的文件吗」,答案是读不到。而且不是它不想读:服务器和我的电脑之间,没有路。
一、为什么没有路
服务器有公网 IP,我的电脑没有。它躲在路由器和公司网络后面,从外面主动来敲它,敲不到——这就是 NAT。
一台公网服务器想主动连我家里的电脑,就像收件人只写了「某某小区」和一个房号,快递员站在小区门口不知道该进哪栋楼。
二、那就反过来走
正向走不通,就把方向翻过来:
不是服务器连我的电脑,而是我的电脑主动连出去。
只要连接是我这边主动发起的,NAT 一律放行——平时刷网页走的就是同一条路。连上之后,再让我的电脑把自己的 SSH 端口「挂」到服务器那头:
我的电脑 (WSL) ──主动连出去──▶ 服务器:22 ▲ │ └──── 服务器 localhost:2222 ─────┘ (AI 从这里进来)一条命令就是这件事:
ssh -N -R 2222:localhost:22 user@服务器-R 就是 reverse:把我本地的 22 端口,映射到服务器那边的 2222 上。之后在服务器上敲 localhost:2222,对面就是我的电脑。
好处很实在——三件事都不用做:
- 我那台电脑不用有公网 IP
- 不用改路由器端口映射
- 不用给 Windows 防火墙开入站端口
服务器那头也没有多暴露什么:-R 默认只绑 127.0.0.1,这个 2222 从公网扫不到,只有服务器自己能连。
三、走 WSL,不走 Windows 原生 SSH
我电脑上有 WSL(Windows 里跑的那个 Linux)。一开始想的是走 Windows 自带的 SSH 服务,试了一圈放弃了:
| Windows 原生 sshd | WSL | |
|---|---|---|
| 装它要 | 管理员权限、Add-WindowsCapability、另找一套 authorized_keys、icacls 调权限 | apt install openssh-server |
| 连上之后落在 | cmd.exe 里 | bash 里 |
| 能看到 | 只有 Windows | Windows 全盘(/mnt/c)+ Linux |
第三条是关键。WSL 里能直接看到 Windows 的全盘,所以一条通道,两个系统都覆盖了。我的交叉编译工具链也都在 WSL 里——连上去不只能读文件,还能直接干活。
四、服务器这头,先备三样
1. 给这台机器配一把专用钥匙,不和别的复用:
ssh-keygen -t ed25519 -f ~/.ssh/pc_tunnel -N "" -C "pc-tunnel-from-<主机>-pc"公钥给本地那台机器,私钥留在服务器上——以后服务器就是拿这把私钥去开本地那扇门的。
2. 确认落点端口空着:
ss -tln | grep ':2222'3. 确认 sshd 允许端口转发(默认允许,但值得确认而不是假设):
sudo sshd -T | grep -Ei 'allowtcpforwarding|gatewayports'五、本地这头,一条命令
把安装脚本挂到服务器已有的 nginx 静态目录下,本地一条命令拉下来跑:
curl -fsSL https://<你的域名>/setup-wsl-tunnel.sh -o /tmp/st.sh && bash /tmp/st.sh脚本替我做六件事:装 openssh-server 和 autossh → 起 sshd → 授权服务器的公钥 → 生成本机钥匙并推给服务器 → 建一个 systemd 服务把隧道挂起来(Restart=always,断了自愈)→ 建一个 Windows 计划任务,开机登录时把 WSL 唤醒(不然 WSL 没启动,隧道自然也不在)。
六、坑一:域名解析不了
装完第一步就撞墙:
curl: (6) Could not resolve host先别急着骂网络。 我让它先做一件最省事的事——分层排查,把「网不通」和「解析不了」分开:
ping -c2 -W2 223.5.5.5 # 纯网络,不走 DNSgetent hosts <域名> # 纯解析结果:
2 packets transmitted, 2 received, 0% packet loss, time 1001msIP 通,域名不通。这就是纯解析问题,网络本身是好的。
接着看解析配置:
cat /etc/resolv.confnameserver 10.255.255.254search <公司内网域>答案就在这两行里。
WSL 自己不做域名解析——它把「问域名」这件事转交给 Windows 去办(10.255.255.254 就是那个转交口)。但我这台 Windows 连着公司 VPN,DNS 被公司接管了。于是就出现:WSL 去问「这个域名在哪」,公司 DNS 说「那不是我们内网的域名」,两边都卡住。
修法是就地补一个能用的 DNS,同时保留公司那个兜底:
cp /etc/resolv.conf /etc/resolv.conf.bakprintf 'nameserver 223.5.5.5\nnameserver 119.29.29.29\nnameserver 10.255.255.254\nsearch <公司内网域>\n' > /etc/resolv.confgetent hosts <域名> && echo "DNS OK"顺手记住一件事:WSL 重启会重新生成这个文件(想永久生效要在 /etc/wsl.conf 里关掉自动生成)。但它不影响我们的隧道——隧道是拿 IP 连服务器的,压根不吃 DNS。坏的只是 apt。
这一条的教训:把「网不通」和「解析不了」拆开测,两分钟就能定位,比在那儿猜半小时强。
七、坑二:脚本静默失败
脚本跑到第 4 步,输出这么一句:
自动推送没成功。把下面这一整行发微信给素一,我手动加:当时看起来像网络问题,其实是我自己写的脚本把自己坑了。
那一步要和我做的是 ssh-copy-id——把本机的公钥推给服务器,推的时候需要输一次服务器的密码。而我在脚本里写的是:
ssh-copy-id "$SERVER" >/dev/null 2>&1把输出扔进黑洞,顺手连密码提示一起扔了。我根本看不到「请输入密码」,脚本干等三次,超时,静默失败。
修法很简单,让提示正常显示出来:
if ssh -o BatchMode=yes "$SERVER" true 2>/dev/null; then ok "已经免密了"else ssh-copy-id "$SERVER" || { echo "把下面这行发过来手动加:"; cat ~/.ssh/id_ed25519.pub; }fi教训:涉及交互式密码的调用,永远不要把输出重定向掉。看着「干净」,实际上是把自己的眼睛蒙上。
顺带,兜底路径也是有用的——我顺手把这把公钥发过去让服务器手动加,十秒就成了,比在那边继续折腾快得多。
八、坑三:把自己认错了
中途还有一次乌龙:我以为电脑上有两个 WSL,分别进去看了一眼,结果两边的输出一模一样:
Ubuntu 24.04 LTSroot@<主机名>后来才反应过来——第二个「环境」根本不是另一个发行版,而是同一个 WSL 的 .bashrc 里自动 source 了交叉编译 SDK 的环境脚本,进来自动 cd 到别处、打印一堆路径。看着像另一台机器,其实是同一台开机自报家门。
教训:环境不对的时候,先看 cat /etc/os-release 和 hostname,别凭启动时那几行输出判断。
九、验收:别只看中间状态
脚本最后一行写着「装完了」,但那时候隧道其实还没通。所以验收必须真的走一遍:
# 服务器侧:隧道口起来了没有ss -tln | grep ':2222'
# 本地侧:两个服务是不是 enabled(enabled 才代表重启会自己活)systemctl is-enabled ssh hermes-tunnelsystemctl is-active ssh hermes-tunnel
# 本地侧:到服务器的连接在不在ss -tnp | grep <服务器IP>
# 真的登一次ssh -p 2222 -i ~/.ssh/pc_tunnel <用户>@localhost 'whoami; hostname'全绿才算数。实际跑完的结果:
LISTEN 127.0.0.1:2222 # 隧道口在听ssh enabled / activehermes-tunnel enabled / activeESTAB ... <服务器IP>:22 # 连接establishedroot@<主机名> # 真的进去了「服务在跑」和「能连上」是两码事。 中间状态(端口在听、进程活着)都只能说明一半,最后必须真的敲一次门、看看有没有人应。
十、这条路给了我什么,怎么关掉
给的是:那台电脑的完整账号权限——读文件、改文件、跑命令;通过 /mnt/c 连 Windows 全盘都在里面。
代价是:它只在电脑开着、WSL 活着的时候有效。关机、wsl --shutdown、或者干脆没开机,通道就断了。这不是常驻后门,是「钥匙插在门上」那一种。
所以开关一定要放在明面上:
sudo systemctl stop hermes-tunnel && sudo systemctl disable hermes-tunnel想恢复就把 stop / disable 换成 enable --now。
十一、几句实在话
整件事做下来,真正的技术含量并不高——核心就是把方向反过来:主动连出去,而不是等人连进来。剩下的全是细节,而细节全都在坑里。
三个坑,三种不同的错法:一个是网络的(DNS 被公司 VPN 接管),一个是自己写的(把密码提示重定向掉了),一个是判断的(把同一个环境认成两个)。它们的共同点是:症状都在别处,根因都不在症状那儿。
排查的价值不在「我知道这个命令」,而在肯把问题一层一层拆开:网通不通?解析通不通?端口在不在听?服务是不是 enabled?最后真的连一次。
把这些拆完,路就通了。
补记:这条通道现在连着我的开发环境,需要的时候它能直接进来读工程、跑编译。工具的意义大概也就是这样——不是炫耀它能做什么,而是让那些原本要来回倒腾的琐事,安静地消失。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!







