SSH 反向隧道总断,我把它交给 autossh 和 systemd 才省心

举报
phantomxjc 发表于 2026/10/14 08:54:47 2026/10/14
【摘要】 内网机器没有公网 IP,用 ssh -R 把服务带出去,结果隧道隔三差五假死。记录一下 GatewayPorts、ServerAliveInterval 和 systemd 托管的踩坑过程。

起因

单位内网有一台机器跑了个内部看板,端口 5000,只在局域网能看。外头有一台云服务器有公网 IP,我就想着用 SSH 反向隧道把它带出去,省得折腾内网穿透那套。

第一版命令很朴素:

ssh -fN -R 2222:localhost:5000 user@vps

-f 后台跑,-N 不执行远程命令,-R 的含义是:在 VPS 上开 2222 端口,收到流量就通过这条 SSH 连接转发回我内网机器的 5000。

命令执行完没报错,但我在外面 curl http://vps-ip:2222 一直是 Connection refused。

第一个坑:默认只监听 127.0.0.1

这是 -R 最容易踩的一点。不加参数时,远端开的端口只绑在 127.0.0.1 上,也就是只有 VPS 自己能访问,外网进不来。

去 VPS 上确认一下就很清楚:

ss -lntp | grep 2222
# LISTEN 0 128 127.0.0.1:2222 0.0.0.0:*

看到 127.0.0.1 就说明绑错了。改法是两边一起动:

服务端 /etc/ssh/sshd_config 加一行:

GatewayPorts clientspecified

改完 systemctl restart sshd。这里顺便说一下,GatewayPorts 有三个值,no 是默认,yes 是强制全部绑 0.0.0.0(不太安全),clientspecified 是由客户端决定绑哪个地址,我一般用这个。

然后客户端显式写上绑定地址:

ssh -fN -R 0.0.0.0:2222:localhost:5000 user@vps

再 ss -lntp 就能看到 0.0.0.0:2222 了,curl 也通了。

第二个坑:进程还在,隧道已经死了

真正麻烦的是后面。这条隧道跑个一天半天就废了,但 ps aux | grep ssh 看进程还好端端在那儿,VPS 上端口也还在 LISTEN,就是 curl 的时候一直挂着不返回,直到超时。

原因是中间的网络设备(NAT、防火墙)静默把空闲连接掐了,两边都没收到 FIN 包,于是双双以为连接还活着。TCP 层面没察觉,SSH 自己也不主动发探测包,就成了假死。

解决办法是让 SSH 自己在通道里发心跳:

ssh -fN \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes \
  -R 0.0.0.0:2222:localhost:5000 user@vps

ServerAliveInterval=30 表示 30 秒没数据就发一个探测;ServerAliveCountMax=3 表示连续 3 次没回应就认定连接已死,退出。注意这个探测是走加密通道内部的,比 TCPKeepAlive 那种空 TCP ACK 靠谱,后者经常被中间设备糊弄过去。

ExitOnForwardFailure=yes 也挺关键,默认是 no,端口被占用时 ssh 只打个警告然后继续跑,看着像成功了其实没转发。加上之后会直接失败退出,方便让外层去重启。

第三步:换成 autossh

心跳只能发现问题,不能自己重连。autossh 就是干这个的,它盯着 ssh 子进程,退出了就重新拉起来。

sudo apt install autossh
autossh -M 0 -f -N \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes \
  -R 0.0.0.0:2222:localhost:5000 user@vps

-M 0 是把 autossh 自带的监控端口关掉。它默认会额外开一对端口做回声检测,我还得在公网机上给它放行,麻烦,而且老被端口扫描扫到。既然 ssh 自带的 ServerAlive 已经能探活,就让 ssh 自己判断、autossh 只管重启。

这里还有个隐式要求:必须配好密钥登录。交互式输密码的话 autossh 重启时卡在密码输入上,永远起不来。

ssh-keygen -t ed25519
ssh-copy-id user@vps

第一次连新 VPS 还会卡在 Are you sure you want to continue connecting (yes/no),自动化场景下加一句 -o StrictHostKeyChecking=accept-new,它只接受未知主机的密钥、但已知主机指纹变了仍然拒绝,比直接 no 安全一点。

第四步:用 systemd 兜底

-f 后台 fork 出来的进程没人管,机器重启就没了。所以我最后把它写成了一个服务:

[Unit]
Description=Reverse SSH tunnel for dashboard
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=tunnel
Environment=AUTOSSH_GATETIME=0
ExecStart=/usr/bin/autossh -M 0 -N \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes \
  -o StrictHostKeyChecking=accept-new \
  -i /home/tunnel/.ssh/id_ed25519 \
  -R 0.0.0.0:2222:localhost:5000 user@vps
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

AUTOSSH_GATETIME=0 一定要设。autossh 默认会等首次连接成功一小段时间,超时就自杀;设成 0 是关掉这个逻辑,否则开机时网络还没起来,它先把自己干掉了。

sudo systemctl daemon-reload
sudo systemctl enable --now tunnel.service
systemctl status tunnel
journalctl -u tunnel -f

安全上补两刀

隧道专用账号我只给了端口转发权限。VPS 的 /etc/ssh/sshd_config 末尾加:

Match User tunneluser
    AllowTcpForwarding remote
    GatewayPorts clientspecified
    PermitOpen 127.0.0.1:*
    ForceCommand /bin/false

这样就算密钥泄露,对方也只能转发、拿不到 shell。

现在的状态

跑了三周没再断过。我还在 VPS 上加了个五分钟一次的探活,curl 那个端口,连续两次失败就发通知到群里——毕竟隧道自愈归自愈,内网那台机器真宕机了它也是白搭,这两件事得分开看。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。