ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Windows下用autossh实现SSH隧道断线自动重连的完整指南

Windows下用autossh实现SSH隧道断线自动重连的完整指南 干了这么多年运维和 DevOps我太知道 SSH 连接断掉有多烦了。尤其是你人在外面电脑合盖换个网络回头一看终端里的会话已经僵死远程跑着的服务没挂倒是你这边的长连接先躺了。更头疼的是那些依赖 SSH 隧道、端口转发才能工作的场景比如跨机房的数据同步、安全访问内网服务、穿透 NAT 连回办公室机器一旦隧道断了整条链路直接瘫痪。以前在 Linux 上解决这个问题有一套很成熟的方案叫 autossh它就是专门干这个的盯住 SSH 进程发现连接断了就立刻自动重连完全不需要人工干预。但问题在于autossh 是个 Unix 环境下的工具Windows 没法直接跑。那 Windows 上能不能实现同样的效果能而且折腾好之后同样稳定、同样省心。这篇文章就是来填这个坑的我会一步步演示怎么在 Windows 上把 autossh 做成一个开机自启、断线自动重连的可靠服务解决你的真实痛点。1. 内容整体设计与思路拆解1.1 autossh 到底解决了什么问题先说清楚 autossh 的工作原理不然你后面配置参数的时候会一头雾水。autossh 本身不是一个独立的传输工具它本质上是一个“监工”启动一个 SSH 进程然后每隔一段时间检查这个进程是否还活着。具体机制上传统做法是让 autossh 额外监听一个端口用它来探测 SSH 连接的健康状态如果探测失败autossh 就认定连接已经断了于是杀掉旧进程、拉起一个新的 SSH 进程重新建立隧道。但现在更推荐的做法是使用-M 0参数也就是禁用 autossh 自己的监控端口转而依赖 SSH 自带的保活机制。SSH 有个配置项叫ServerAliveInterval会在指定秒数内向对端发送一个心跳包如果连续收不到回复SSH 客户端就会认为连接出了问题并主动退出。autossh 看到 SSH 进程退出后就会立即重启一个新的 SSH 进程。这套机制的妙处在于不需要额外占用端口也避开了防火墙对未知端口的干扰可靠性反而更高。理解了这个逻辑你就知道在 Windows 上折腾 autossh 的思路其实就三条找一条路径让 autossh 能在 Windows 环境里运行把 SSH 密钥、连接参数配好保证重连时不需要手动输入密码最后把它做成一个系统服务让它开机自启、退出自动拉起。所以真正的复杂度不在于 autossh 本身而在于如何把它嵌入 Windows 的服务生态。1.2 为什么 Windows 原生跑不了 autosshautossh 之所以在 Windows 上没有官方原版是因为它重度依赖 POSIX 语义比如 fork、exec、信号处理这些机制。Windows 的进程模型和 Unix 完全不同直接移植的工程量不小而且它作为一个小众运维工具维护者也一直没精力去做原生支持。这就逼着我们不得不走“曲线救国”的路线。目前主流的做法有三类。第一类是 WSLWindows Subsystem for Linux也就是在 Windows 里跑一个 Linux 子系统让 autossh 跑在 Linux 环境下这也是我最推荐的方案原因后面详细说。第二类是 MSYS2 / Cygwin它们能在 Windows 上模拟一个 POSIX 环境也能编译运行 autossh但路径转换、权限模型偶尔会出幺蛾子适合爱折腾的玩家。第三类是纯 PowerShell 脚本方案就是自己写循环用Start-Process启动 ssh再通过轮询进程状态来触发重连这个方案不依赖任何模拟环境但逻辑要自己写健壮性和美观程度都有限。我在实际项目中三种方案都试过最终固定下来的是 WSL 方案。原因非常现实WSL 环境干净、依赖齐全、和原版 autossh 的兼容性最好而且调试方便日志清晰。MSYS2 也能用但你在网上找到的大多数 autossh 教程都是 Linux 语法在 WSL 里你甚至不需要改一行命令就能直接照搬这对初学者来说节省了太多弯路。1.3 三条路线怎么选一张表看懂方案优点缺点适合人群WSL环境干净、兼容性好、和 Linux 教程无缝衔接需要安装 WSL 子系统初次配置稍麻烦大多数场景推荐首选MSYS2 / Cygwin轻量、不需要额外子系统路径转换和权限偶有怪异问题临时使用、不想装 WSL 的场景PowerShell 脚本零依赖、纯 Windows 原生逻辑自己写稳定性需要自行保证只做简单端口转发、不想引入额外组件在后面的实操部分我会以 WSL 方案为主线把整个安装到服务化的流程完整走一遍同时在下文提到 MSYS2 的替代做法方便你在不同环境里做取舍。2. 环境准备与工具选型解析2.1 Windows 上准备 WSL 环境在装 autossh 之前先把 WSL 跑起来。Windows 10 2004 及以上版本、Windows 11 都支持 WSL现在是 WSL2性能和兼容性都远好于第一版。打开 PowerShell管理员权限直接运行wsl --install这个命令默认会安装 Ubuntu 最新 LTS 版本同时启用 WSL 相关功能组件。装完之后系统会提示你重启重启完继续根据终端提示初始化 Linux 用户名和密码。如果你已经装了 WSL只是想查看当前发行版状态可以用wsl -l -v看到 Ubuntu 状态是 Running 或 Stopped 都正常只要版本是 2 就说明走的是 WSL2 架构。这里有个细节值得注意WSL2 的网络模式默认是 NAT 型也就是说 WSL 里的 Linux 环境和 Windows 宿主共享一个虚拟网络WSL 对外发起 SSH 连接时流量会从宿主机的网络栈出去这对于我们要做 SSH 隧道来说没有任何影响因为出方向连接完全正常。唯一需要注意的是如果你想从外部访问隧道暴露的端口要在防火墙里放行对应的 Windows 宿主机端口。装好 WSL 之后进入 Ubuntu 环境先做一轮系统更新sudo apt update sudo apt upgrade -y接着安装 autossh 和 OpenSSH 客户端sudo apt install -y autossh openssh-client如果你在这一步遇到了找不到软件包的问题多半是 Ubuntu 的软件源还没刷出来先跑一遍sudo apt update再装基本都能解决。2.2 SSH 密钥配置免密登录是自动重连的前提autossh 做自动重连的前提是连接建立的过程不需要人工输入密码否则断线后 autossh 拉起新进程卡在密码提示符那里重连就变成了摆设。所以 SSH 密钥认证必须提前配好。在 WSL 的终端里生成密钥ssh-keygen -t ed25519 -a 100生成过程中直接按三次回车使用默认路径和空密码。这样生成的密钥位于~/.ssh/id_ed25519。选 ed25519 的原因很简单它比 RSA 更安全、密钥更短、生成更快而且现在绝大多数服务器都支持。如果你的目标服务器比较老不支持 ed25519那就退回去用ssh-keygen -t rsa -b 4096生成后把公钥上传到服务器我用的是一个很通用的方法——ssh-copy-id这工具在 Ubuntu 上默认装了直接执行ssh-copy-id useryour-server-ip它会要求输入一次服务器密码之后公钥就写进服务器的~/.ssh/authorized_keys文件了。万一你的服务器禁止密码登录那你需要手动把公钥内容追加到服务器的 authorized_keys 里。注意权限服务器上~/.ssh目录权限必须是 700authorized_keys文件权限必须是 600否则 sshd 会拒绝使用密钥认证。验证免密登录是否成功ssh useryour-server-ip echo ok能输出ok就说明密钥配好了。这一步做扎实了后面 autossh 重连才能做到真正的无声无息。2.3 NSSM 服务包装工具让 autossh 变成 Windows 服务WSL 里的 autossh 只是一个普通的前台进程如果没有特殊处理WSL 发行版一停止进程就没了更谈不上开机自启。因此需要把它注册成 Windows 服务我用的工具是 NSSMNon-Sucking Service Manager这是一个广受好评的开源服务包装器可以把任意可执行文件注册为系统服务并且对进程退出有响应的重启策略设置。NSSM 的下载很简单打开它的官网下载 zip 压缩包解压后根据你系统的位数选择目录里面是nssm.exe这个可执行文件。建议把它放到一个固定路径比如C:\nssm\nssm.exe方便后面命令行调用。选 NSSM 而不是直接用 Windows 自带的sc命令原因有三个第一NSSM 可以通过命令行或图形界面直观地配置服务的启动参数、工作目录和依赖关系试错成本低第二NSSM 自带进程崩溃后自动重启的机制可以针对服务设置多种退出策略第三NSSM 能把 stdout 和 stderr 重定向到日志文件这对排查 autossh 故障非常关键。如果你实在不想用 NSSM也可以用 Windows 任务计划程序把开机的触发动作设成运行一条 wsl.exe 命令。这个方案更轻但缺点是任务计划程序对服务状态不可感知进程被意外杀掉后不会自动拉起稳定性不如 NSSM。所以还是推荐 NSSM。3. 实操过程与核心环节实现3.1 用 autossh 建立自动重连的 SSH 隧道直接说命令。假设你的目标服务器是user203.0.113.5这台机器开启远程访问端口你想在本地Windows也提供了一个远程访问入口把它绑定在本机的 2222 端口上把流量转发到服务器的 22 端口上那这条正向隧道可以这样建立autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -L 2222:localhost:22 user203.0.113.5这条命令看着长拆开来看就很好懂-M 0禁用 autossh 自己的监控端口。前面说过了靠 SSH 自带的保活参数来检测连接状态这样最省心。-N建立连接后不要执行远程命令纯端口转发场景下必带。-fautossh 在后台运行。注意这个行为是 autossh 自己实现的它会 fork 到后台并把 SSH 进程托管起来。-o ServerAliveInterval 30每 30 秒发送一次保活心跳。-o ServerAliveCountMax 3连续 3 次心跳没收到回复就认为连接已死。也就是说90 秒没收到对端回应SSH 客户端会主动退出。-L 2222:localhost:22将本地 2222 端口的流量转发到目标服务器的 22 端口。心跳参数的意义在于连接断掉之后客户端可能很久都感知不到尤其是双方都没有数据交互的时候。如果不加这个参数autossh 也检测不到进程异常因为很多时候 SSH 进程还在“傻傻地等着”连接其实已经死了。所以这两个-o参数是解决“假死”现象的关键务必加上。如果你需要的是反向隧道也就是在服务器上开一个端口指向你的 Windows 机器上的某个服务命令稍有不同。比如我在家用电脑上部署了一个内网穿透场景希望在任何地方都能通过服务器的 8080 端口访问到家里这台 Windows 上跑的 web 服务监听 3000 端口那就是autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -R 8080:localhost:3000 user203.0.113.5-R参数的含义是把服务器上的端口映射回本地这里就是把服务器的 8080 端口流量导向 WSL 的 3000 端口再由 WSL 通过 localhost 转发到 Windows 宿主机。由于 WSL2 的网络隔离特性这里访问localhost:3000其实访问的是 WSL 自己的回环地址需要通过下述方式做一层映射才能到 Windows 宿主机不过大多数场景中我们直接在 WSL 里跑应用服务就没有这个烦恼了。3.2 理解 WSL 与 Windows 宿主端口互通上面提到 WSL2 的网络模式是 NAT它和 Windows 宿主各自拥有自己的 IP。WSL 里监听localhost:3000Windows 宿主并不能直接通过 localhost 访问到它。反过来也一样。在用 autossh 做反向隧道时关键的“从服务器反连到本地”的流量入口其实是从 WSL 出发的所以它访问 localhost 是完全没问题的因为 SSH 进程就运行在 WSL 里。但如果你的应用跑在 Windows 宿主机上比如 Windows 上的某个数据库比如 MySQLWSL 里的 autossh 转发目标就需要写成 Windows 宿主机的 IP而不是 localhost。从 WSL 里访问 Windows 宿主机可以用一个特殊域名或者直接查 Windows 的 IP。在 WSL 终端执行ip route show | grep -i default | awk { print $3 }这个 IP 就是宿主机的网关地址。假设输出是172.24.80.1那么反向隧道命令就要改成autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -R 8080:172.24.80.1:3000 user203.0.113.5反过来如果想从 Windows 宿主机访问 WSL 里监听的端口可以在 WSL 里运行ifconfig查看 eth0 的 IP然后从 Windows 浏览器直接访问WSL-IP:端口。但这个 IP 在每次 WSL 重启后可能会变所以一般建议通过 Windows 侧的端口转发机制做一层固定映射。用管理员 PowerShell 执行netsh interface portproxy add v4tov4 listenaddress0.0.0.0 listenport3000 connectaddressWSL-IP connectport3000这样 Windows 宿主机的 3000 端口就固定转发到 WSL 的 3000 端口不随 WSL 的 NAT IP 变化而改变。不过这个操作是可选项很多场景并不需要我只是把它列出来防止你被这一步卡住。3.3 用 NSSM 把 autossh 注册成 Windows 服务前面的步骤已经跑通了手动命令现在来做最关键的一环服务化。以管理员身份打开 PowerShell切换到 NSSM 所在目录执行下面的命令创建服务C:\nssm\nssm.exe install AutosshTunnel执行后 NSSM 会弹出一个图形化配置界面。如果没有弹出也可以用纯命令行方式注册。在图形界面里主要配置四个地方Application 页签Path填C:\Windows\System32\wsl.exeStartup directory可以留空或者填C:\Windows\System32Arguments填-d Ubuntu -u root -e autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -L 2222:localhost:22 user203.0.113.5注意这里的参数结构wsl.exe后面跟的是-d指定发行版名称-u root指定以 root 用户执行-e后面跟要执行的命令及其参数。如果之前生成的 SSH 密钥不在 root 用户目录下而是在你自己的 WSL 用户下那这里-u要写成你自己的用户名同时保证 SSH 密钥路径正确。有时在服务环境下直接指定-u root会带来一些环境变量缺失的问题稳妥的办法是用-u 你的用户名然后在配置里通过ssh -i /home/用户名/.ssh/id_ed25519显式指定密钥文件避免加载不到~/.ssh下的默认 key。命令变成-d Ubuntu -u yourname -e autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -o ExitOnForwardFailure yes -L 2222:localhost:22 -i /home/yourname/.ssh/id_ed25519 user203.0.113.5加-o ExitOnForwardFailure yes的好处是如果端口绑定失败比如本地 2222 端口已被占用SSH 不会继续默默运行而是直接退出autossh 会重新尝试这样能提高失败恢复的速度。Log on 页签推荐选“Local System account”这是默认选项对大多数隧道转发场景足够了。如果要访问网络共享资源那就需要指定账户。Shutdown 页签设置服务停止时的行为一般不用改默认会在超时后强制终止进程。如果发现服务停止特别慢可以在这里设短一点的超时时间。AppExit 页签设置进程退出时的处理动作。由于 autossh 本身已经具备断线重连能力这里的退出一般指 WSL 进程被杀掉所以设成 Restart重启即可。但这个配置在 NSSM 图形界面里可能需要加一行规则最简单的方式是用命令行后再手工修改。配置好之后在 PowerShell 里启动服务C:\nssm\nssm.exe start AutosshTunnel查看服务状态C:\nssm\nssm.exe status AutosshTunnel看到SERVICE_RUNNING就说明服务已经起来了。这时候你可以尝试去连一下本地的 2222 端口看隧道是否通ssh -p 2222 localhost如果能顺利登录服务器说明整条链路已经打通。3.4 一次完整的配置案例复盘拿我自己的经历举个例子。我之前在家里的 Windows 机器上部署了一套内部工具需要让公司电脑随时随地能访问家里几个服务包括一台运行在 WSL 里的状态面板端口 8081和一台 Windows 上的开发数据库端口 3306。我的服务器是一台云主机公网 IP 为203.0.113.5。我在 WSL 的/home/me/.ssh/下生成了密钥把公钥写到了服务器的 authorized_keys。然后我注册了两个 NSSM 服务一个做反向隧道wsl.exe -d Ubuntu -u me -e autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -o ExitOnForwardFailure yes -R 8081:localhost:8081 -i /home/me/.ssh/id_ed25519 me203.0.113.5另一个做数据库端口的反向映射wsl.exe -d Ubuntu -u me -e autossh -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -o ExitOnForwardFailure yes -R 13306:172.24.80.1:3306 -i /home/me/.ssh/id_ed25519 me203.0.113.5这个例子里的数据库端口是 Windows 宿主机上的所以转发目标是172.24.80.1我先用ip route查到了宿主 IP。两个服务都设为开机自启。我用 NSSM 的 AppExit 规则在进程意外退出时自动重启实测跑了三个多月中间跨过多次网络切换、家里路由重启、云主机短暂掉线隧道都能在几十秒内自动恢复。这就是 autossh 在 Windows 上正常工作的完整形态。3.5 MSYS2 替代方案简述如果你实在不想装 WSLMSYS2 兜底方案也可以跑 autossh。安装 MSYS2 后在其自带的 MSYS2 UCRT64 终端里执行pacman -S mingw-w64-ucrt-x86_64-autossh openssh装好之后命令就能直接用。但要注意 MSYS2 的环境变量和路径系统和原生 Windows 不同写 NSSM 服务参数时路径要非常小心有时候 autossh 会找不到 ssh 可执行文件需要手动指定--eth0不太稳妥或者在 PATH 里把 MSYS2 的 usr/bin 目录加上。相比之下我还是推荐 WSL因为少踩坑远比省几分钟更值钱。4. 常见问题与排查技巧实录4.1 服务已启动为什么隧道就是不通这是出现率最高的问题。服务状态明明是 Running但隧道要么先建后断要么根本没建起来。我的排查路径很有规律基本按下面这张表来走现象检查点处理思路本地端口无法连接隧道方向是否写反确认-L是本地端口映射到远程端口-R是远程端口映射回本地服务器上端口未监听服务器防火墙/安全组策略在服务器执行 ss -tlnp远程端口连接马上被重置服务器 sshd 配置限制查看MaxStartups、AllowTcpForwarding是否限制转发隧道建立几秒后断开心跳参数缺失加上ServerAliveInterval和ServerAliveCountMax后再看日志我遇到过最隐蔽的一次是 Windows 防火墙拦截了 WSL 的虚拟网络接口导致 WSL 里发出去的 SSH 连接被中断。处理办法是在防火墙高级设置里放行 WSL 应用的网络流量或者临时关掉 Windows 防火墙做对照实验来确认是不是防火墙拦截。4.2 autossh 在 WSL 里日志怎么看排查问题不能靠猜日志是唯一的参考依据。autossh 默认不打印详细日志为了配合 NSSM 的日志重定向我在 NSSM 配置的 Arguments 里加入了-v参数wsl.exe -d Ubuntu -u me -e autossh -v -M 0 -N -f -o ServerAliveInterval 30 -o ServerAliveCountMax 3 -o ExitOnForwardFailure yes -R 8081:localhost:8081 -i /home/me/.ssh/id_ed25519 me203.0.113.5然后在 NSSM 的 I/O 页签里设置 stdout 和 stderr 重定向到固定文件比如C:\logs\autossh.out.log。这样 autossh 和 ssh 的 verbose 输出就能直接写到 Windows 文件系统里。排查问题时打开这个日志看到Server responded to global request说明链路已经建立看到Connection reset by peer、Connection timed out之类就能把问题锚定在网络层。不过要注意-f参数会让 autossh fork 到后台这时候 NSSM 可能认为进程已经退出并再次尝试重启形成循环。这实际上是个潜在坑我在实践中发现服务和 autossh 的-f有时会冲突。我通常在 NSSM 注册的 autossh 命令里去掉-f参数让 autossh 以前台方式运行由 NSSM 来托管生命周期。这样更稳定也不影响服务注册。只不过如果你去掉-f原来一段在 WSL 终端里手动运行的命令行为会变化所以这一点要刻意区分。4.3 服务开机自启后连不上但手动启动服务正常如果手动启动服务一切正常重启电脑后却失联很大概率是服务启动时网络还没就绪。NSSM 默认启动服务不等待网络但 SSH 连接本质上需要网络栈已经加载。解决思路有两个第一个思路是在 NSSM 里设置服务依赖让它晚于网络相关服务启动。NSSM 图形界面的”Dependencies“页签里加上对Tcpip、DHCP、Dnscache的依赖。其实最稳妥的依赖关系是在服务属性的“依赖服务”里加上LanmanWorkstation或者干脆设置成“自动延迟启动”。这个具体操作起来使用命令行更快C:\nssm\nssm.exe set AutosshTunnel DependOnService Tcpip C:\nssm\nssm.exe set AutosshTunnel Start SERVICE_DELAYED_AUTO_START第二条种思路是在 WSL 侧加一个启动等待脚本比如在 Ubuntu 的/etc/profile.d/下加一段 wait-for-network.sh等网络就绪后再启动服务。不过由于 NSSM 启动的是 wsl.exe这个脚本需要配合 systemd 或其他机制比较复杂。我更推荐直接用 NSSM 的延迟启动实测最有效。4.4 WSL 跨 Windows 登录会话自启服务NSSM 注册的服务默认以系统账户运行不会附属于某个用户的登录会话。这意味着 WSL 发行版如果是在某个用户登录后才自动挂载的服务里面的 wsl.exe 调用可能会失败。一个常见的错误是The operation was canceled by the user或者服务长时间没反应。处理方法是在 NSSM 的 Log on 页签里选择指定账户输入你的 Windows 登录账号和密码。这样服务就以你的用户身份启动 WSL能正确挂载这个用户的发行版实例和相关网络资源。4.5 服务器端 sshd 配置优化建议autossh 的稳定性不只看客户端服务器端也要配合。之前碰过一个问题隧道断掉后服务器上没有立即回收端口导致 autossh 重连时端口被占新的连接失败反复几次才成功一次。后来在服务器的 sshd_config 里调整了 TCPKeepAlive 等参数情况明显好转。与 autossh 相关的几个常用服务器端设置TCPKeepAlive yes ClientAliveInterval 30 ClientAliveCountMax 3TCPKeepAlive保证 TCP 层有心跳ClientAliveInterval让服务器主动向客户端发保活包ClientAliveCountMax决定多少个保活包无响应后断开连接。服务器端这些参数的意义在于网络异常时不会把连接悬在半空而是快速清理让 autossh 得以立刻重连。改完配置记得重启 sshdsudo systemctl restart sshd这步不复杂但对长期稳定运行帮助很大。写到这里其实能讲的都差不多讲透了。我在 Windows 上跑 autossh 也踩过不少坑尤其是早期用任务计划程序而不是服务方式来托管导致开机后经常连不上后来换到 NSSM 管理加了网络依赖和延迟启动稳得多了。文章里哪个卡住了可以先从日志着手再配合我上面整理的排查思路去验证一步一步来。最后再分享一个让我印象比较深的小技巧注册完服务后记得在 NSSM 的 I/O 页签里把 stderr 和 stdout 分别重定向到两个日志文件我现在排障都靠这两个文件定位问题另外密钥文件的权限也好以及 WSL 里用户目录的 .ssh 权限也好一定要设对很多莫名奇妙的重连失败其实就是权限问题导致的读取失败。这套方案我自己跑了大半年从最开始手动 ssh 看盘变成完全无人值守的自动重连说实话省下来的时间足够我再折腾好几个项目了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进