爱和冰阔落头像
关注
【Linux】没有公网 IP 也能 SSH 回内网:反向 SSH、ProxyJump 与 systemd 保活实战封面图

【Linux】没有公网 IP 也能 SSH 回内网:反向 SSH、ProxyJump 与 systemd 保活实战

封面

🔥个人主页:爱和冰阔乐
📚专栏传送门:《数据结构与算法》C++
🐶学习方向:C++方向学习爱好者
⭐人生格言:得知坦然 ,失之淡然

在这里插入图片描述


🏠博主简介
在这里插入图片描述


前言

实验室电脑、工控机、家庭 NAS 和边缘设备经常有一个共同问题:

设备能访问互联网
外面却不能主动连接设备

原因可能是:

  • 没有公网 IPv4;
  • 在公司或校园 NAT 后面;
  • 路由器不归自己管理;
  • 运营商使用 CGNAT;
  • 防火墙不允许入站;
  • 设备地址经常变化。

这时给内网设备安装远控软件当然可以,但如果需求只是:

在外面通过 SSH 维护一台 Linux 机器

反向 SSH 隧道更轻。

它的思路不是让公网服务器主动打进内网,而是让内网机器先连接一台有公网地址的服务器,并在服务器上反向开放一个端口。

本文会完成一条可长期运行的链路:

笔记本
   ↓ SSH
公网跳板机:22022
   ↓ 已建立的反向隧道
内网设备:22

同时处理几个真正容易出问题的地方:

  • 为什么 ssh -R 成功却连不上;
  • 端口绑定在 127.0.0.1 还是 0.0.0.0
  • 网络抖动后怎样自动重连;
  • systemd 的 Restart=always 为什么仍可能假在线;
  • 如何限制专用账号,避免跳板机变成任意转发器;
  • 怎样判断故障发生在内网设备、隧道、跳板机还是客户端。

Linux 反向 SSH 隧道封面


一、反向隧道到底做了什么

1.1 普通本地转发

本地转发常见写法:

ssh -L 8080:internal-web:80 user@jump-server

含义:

本机 8080
通过 jump-server
访问 internal-web:80

1.2 反向转发

反向转发:

ssh -R 22022:127.0.0.1:22 [email protected]

这条命令由内网设备执行。

含义:

在远端服务器监听 22022
连接进入 22022 后
通过当前 SSH 会话转发到内网设备的 127.0.0.1:22

注意两个 127.0.0.1 的位置不同:

远端监听地址:默认通常只在跳板机回环地址监听
目标 127.0.0.1:22:相对于发起 ssh -R 的内网设备

1.3 数据流

从外部笔记本连接时:

ssh client
  → jump-server:22022
  → reverse tunnel
  → edge-device:127.0.0.1:22
  → edge-device sshd

如果跳板机端口正常监听,但设备本机 sshd 没启动,隧道依然无法完成最终连接。

内网设备主动建立反向 SSH 的概念图

图里的红色箭头被 NAT 墙挡住,说明外部不能直接拨入;橙色绳索由内网设备主动抛向跳板机,外部笔记本再沿现有链路进入。反向隧道改变的是连接建立方向。


二、准备三台角色

本文使用下面的名字:

角色示例地址用途
内网设备edge-box被维护的机器
公网跳板机server.example.com接收反向隧道
外部客户端laptop发起维护连接

跳板机至少需要:

  • 可从公网访问的 SSH 端口;
  • 一个专用 Linux 用户;
  • 允许 TCP 转发;
  • 防火墙允许需要的监听方式。

内网设备需要:

  • 能主动访问跳板机;
  • 本机 SSH 服务可用;
  • 保存专用私钥;
  • systemd。

三、先验证最小命令

3.1 检查内网设备 SSH

在内网设备执行:

systemctl status ssh

不同发行版服务名可能是:

systemctl status sshd

确认本机可连接:

ssh 127.0.0.1

如果这一步失败,先修本机 SSH,不要急着排查隧道。

3.2 创建专用密钥

在内网设备执行:

install -d -m 700 ~/.ssh

ssh-keygen \
  -t ed25519 \
  -f ~/.ssh/reverse_tunnel_ed25519 \
  -C "edge-box reverse tunnel"

无人值守服务需要避免交互式密码。

如果私钥设置口令,就要额外配置 agent 或凭据解锁方案。不要在 systemd 单元中明文写私钥口令。

3.3 创建跳板机专用用户

在跳板机执行:

sudo useradd \
  --create-home \
  --shell /usr/sbin/nologin \
  tunnel

某些系统的 nologin 路径不同:

command -v nologin

创建授权目录:

sudo install \
  -d \
  -m 700 \
  -o tunnel \
  -g tunnel \
  /home/tunnel/.ssh

把内网设备的公钥写入:

sudo tee /home/tunnel/.ssh/authorized_keys

再修权限:

sudo chown tunnel:tunnel \
  /home/tunnel/.ssh/authorized_keys

sudo chmod 600 \
  /home/tunnel/.ssh/authorized_keys

3.4 前台启动隧道

内网设备执行:

ssh \
  -NT \
  -i ~/.ssh/reverse_tunnel_ed25519 \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -R 22022:127.0.0.1:22 \
  [email protected]

参数说明:

参数作用
-N不执行远端命令
-T不分配伪终端
-R建立远端端口转发
ExitOnForwardFailure=yes远端端口绑定失败时退出
ServerAliveInterval=30定期发送保活请求
ServerAliveCountMax=3多次无响应后断开

如果第一次连接询问主机指纹,先人工核对并接受。不要为了省事长期使用:

StrictHostKeyChecking=no

四、先从跳板机本机测试

4.1 检查监听

在跳板机执行:

ss -lntp | grep 22022

可能看到:

LISTEN 0 128 127.0.0.1:22022 0.0.0.0:*
LISTEN 0 128 [::1]:22022       [::]:*

这说明端口默认只绑定跳板机回环地址。

4.2 通过回环地址连接

在跳板机执行:

ssh \
  -p 22022 \
  [email protected]

这里的 edgeuser 是内网设备上的登录用户,不是跳板机的 tunnel 用户。

如果成功,说明:

内网设备 → 跳板机 SSH 会话正常
远端监听正常
隧道到内网设备 22 端口正常
内网设备 sshd 正常

4.3 为什么外部客户端仍连不上

因为监听地址是:

127.0.0.1:22022

公网网卡没有监听。

这不是错误,反而是更安全的默认方式。

外部客户端可以先 SSH 登录跳板机,再使用 ProxyJump 或二次转发,不必把 22022 暴露到公网。

反向 SSH 推荐访问链路

链路中只有跳板机原有 SSH 入口暴露公网。22022 留在回环地址,由 ProxyJump 在跳板机内部访问,减少新增端口暴露。


五、推荐方式:端口只绑定回环地址

5.1 使用 ProxyJump

在外部笔记本的 ~/.ssh/config

Host public-jump
    HostName server.example.com
    User admin
    IdentityFile ~/.ssh/jump_ed25519

Host edge-box
    HostName 127.0.0.1
    Port 22022
    User edgeuser
    IdentityFile ~/.ssh/edge_admin_ed25519
    ProxyJump public-jump

连接:

ssh edge-box

这里有两组密钥:

laptop → jump-server
laptop → edge-box(通过隧道)

不要把隧道服务密钥同时当管理员登录密钥。

5.2 为什么这种方式更稳

跳板机公网只开放原本的 SSH 端口。

反向端口:

只允许跳板机本机访问

攻击面更小,也不需要为每台内网设备额外开放一个公网端口。

5.3 使用显式 ProxyCommand

旧版客户端也可以:

Host edge-box
    HostName 127.0.0.1
    Port 22022
    User edgeuser
    ProxyCommand ssh public-jump -W %h:%p

优先使用 ProxyJump,配置更直接。


六、如果必须直接暴露远端端口

6.1 指定监听地址

内网设备命令:

ssh \
  -NT \
  -R 0.0.0.0:22022:127.0.0.1:22 \
  [email protected]

但这条命令能否绑定公网地址,取决于跳板机 sshd_config

6.2 GatewayPorts

跳板机检查:

sudo sshd -T | grep gatewayports

常见设置:

GatewayPorts no
GatewayPorts yes
GatewayPorts clientspecified

更可控的是:

GatewayPorts clientspecified

允许客户端明确指定监听地址,而不是把所有反向转发默认暴露到公网。

修改后先验证配置:

sudo sshd -t

再重载:

sudo systemctl reload sshd

部分系统服务名是 ssh

6.3 防火墙限制来源

即使监听 0.0.0.0:22022,也不要默认允许全网访问。

以 UFW 为例:

sudo ufw allow \
  from 203.0.113.10 \
  to any port 22022 \
  proto tcp

其中 203.0.113.10 替换为可信客户端出口地址。

云服务器还要检查安全组。

系统防火墙开放,不等于云平台安全组已经开放;反过来也一样。

回环绑定与公网绑定的差异

图中公网绑定不是禁止项,但必须说明为什么需要,并同时限制 GatewayPorts、来源地址和账号权限。默认场景优先选择回环绑定。


七、限制 tunnel 专用账号

7.1 authorized_keys 限制

在跳板机:

restrict,port-forwarding,permitlisten="127.0.0.1:22022" ssh-ed25519 AAAA...

实际支持的选项与 OpenSSH 版本有关,修改后应查看本机:

man sshd

或:

man authorized_keys

restrict 用于关闭多项默认能力,再显式允许端口转发。

permitlisten 限制该密钥可以监听的地址与端口。

7.2 sshd_config Match

也可以在 /etc/ssh/sshd_config.d/tunnel.conf

Match User tunnel
    AllowTcpForwarding remote
    GatewayPorts no
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no

如果确实需要客户端指定监听地址:

GatewayPorts clientspecified

不要直接复制配置后重启。

先检查:

sudo sshd -t

再查看最终展开配置:

sudo sshd -T

7.3 nologin 会不会影响隧道

ssh -N 不请求交互式 shell,但不同系统的 PAM 和 sshd 配置可能对 nologin 有额外限制。

如果连接立即关闭,查看:

sudo journalctl -u sshd -n 100 --no-pager

不要只在客户端反复改参数。

专用账号最终目标是:

能认证
能建立指定反向转发
不能执行任意命令
不能建立其他转发

八、写成 systemd 服务

8.1 为什么不用 ssh ... &

后台符号解决不了:

  • 开机自动启动;
  • 进程异常退出后重启;
  • 日志查看;
  • 启动顺序;
  • 网络恢复;
  • 资源和权限限制。

systemd 更适合长期守护。

8.2 创建专用本地用户

在内网设备:

sudo useradd \
  --create-home \
  --shell /usr/sbin/nologin \
  reverse-ssh

安装私钥:

sudo install \
  -d \
  -m 700 \
  -o reverse-ssh \
  -g reverse-ssh \
  /home/reverse-ssh/.ssh

sudo install \
  -m 600 \
  -o reverse-ssh \
  -g reverse-ssh \
  ~/.ssh/reverse_tunnel_ed25519 \
  /home/reverse-ssh/.ssh/id_ed25519

把已核对的服务器主机密钥写入:

sudo -u reverse-ssh \
  ssh-keyscan \
  -H server.example.com \
  >> /home/reverse-ssh/.ssh/known_hosts

ssh-keyscan 只负责采集,不负责证明主机身份。首次部署应通过可信渠道核对指纹。

8.3 systemd 单元

/etc/systemd/system/reverse-ssh.service

[Unit]
Description=Reverse SSH tunnel to public jump server
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=reverse-ssh
Group=reverse-ssh

ExecStart=/usr/bin/ssh \
    -NT \
    -i /home/reverse-ssh/.ssh/id_ed25519 \
    -o BatchMode=yes \
    -o ExitOnForwardFailure=yes \
    -o ServerAliveInterval=30 \
    -o ServerAliveCountMax=3 \
    -o ConnectTimeout=10 \
    -o StrictHostKeyChecking=yes \
    -R 22022:127.0.0.1:22 \
    [email protected]

Restart=always
RestartSec=5s

NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=read-only

[Install]
WantedBy=multi-user.target

路径必须使用绝对路径。

多行 ExecStart 的反斜杠后不要留下空格。

8.4 启用

sudo systemctl daemon-reload
sudo systemctl enable --now reverse-ssh.service

查看:

systemctl status reverse-ssh.service

实时日志:

journalctl \
  -u reverse-ssh.service \
  -f

九、为什么进程活着,隧道可能已经失效

9.1 TCP 半开连接

网络切换、NAT 超时或中间设备丢状态时,客户端进程可能暂时没有感知。

因此使用:

ServerAliveInterval=30
ServerAliveCountMax=3

大约连续多次无响应后退出,让 systemd 重启。

9.2 ExitOnForwardFailure

如果 22022 已被占用:

没有该参数时,SSH 主连接可能仍保持,systemd 看到进程存活,但转发并未建立。

加上:

ExitOnForwardFailure=yes

端口绑定失败会让进程退出,交给 systemd 重试。

9.3 网络在线目标不是绝对保证

network-online.target 只表示系统的网络管理组件认为网络已准备。

它不保证:

DNS 一定可用
公网一定可达
跳板机一定启动
认证一定成功

所以仍然需要 Restart=always


十、一个跳板机连接多台设备

可以给每台设备分配一个回环端口:

edge-a → 127.0.0.1:22021
edge-b → 127.0.0.1:22022
edge-c → 127.0.0.1:22023

外部客户端配置:

Host edge-a
    HostName 127.0.0.1
    Port 22021
    User edgeuser
    ProxyJump public-jump

Host edge-b
    HostName 127.0.0.1
    Port 22022
    User edgeuser
    ProxyJump public-jump

端口分配要建立清单,避免两台机器抢同一个端口。

更严格的管理方式:

  • 每台设备一个跳板机账号;
  • 每台设备一把密钥;
  • permitlisten 限制对应端口;
  • 日志能定位到具体账号。

共享一个 tunnel 用户虽然配置少,撤销单台设备时却不够方便。


十一、逐层排障

11.1 第一层:内网设备能否访问跳板机

getent hosts server.example.com
nc -vz server.example.com 22

或直接:

ssh -vvv \
  [email protected]

关注:

DNS
TCP connect
主机指纹
公钥认证

11.2 第二层:远端转发是否建立

内网设备前台运行:

ssh -vvv \
  -NT \
  -o ExitOnForwardFailure=yes \
  -R 22022:127.0.0.1:22 \
  [email protected]

跳板机:

ss -lntp | grep 22022

如果没有监听,检查:

  • 端口被占用;
  • AllowTcpForwarding
  • permitlisten
  • 用户认证后被策略拒绝;
  • GatewayPorts 与指定地址。

11.3 第三层:隧道目标能否连接

跳板机:

ssh -vvv \
  -p 22022 \
  [email protected]

如果出现:

connection refused

可能是内网设备的 22 端口没监听。

内网设备检查:

ss -lntp | grep ':22'

11.4 第四层:外部客户端到跳板机

如果跳板机本机能连,外部不能连:

  • 使用回环绑定时,应检查 ProxyJump
  • 使用公网绑定时,检查 GatewayPorts
  • 检查系统防火墙;
  • 检查云安全组;
  • 检查客户端出口地址限制。

11.5 第五层:systemd

systemctl show reverse-ssh.service \
  -p ActiveState \
  -p SubState \
  -p NRestarts
journalctl \
  -u reverse-ssh.service \
  --since "30 minutes ago" \
  --no-pager

反复重启通常不是 systemd 故障,而是:

认证失败
主机密钥变化
端口冲突
DNS 不通
跳板机策略拒绝

反向 SSH 逐层排障命令

排障顺序从服务状态、日志、远端监听到端到端 SSH。只看 systemctl active 不能证明端口真正建立,更不能证明最终登录可用。


十二、常见错误对照表

现象常见原因检查
SSH 主连接成功,22022 不监听转发失败但未退出ExitOnForwardFailure
跳板机本机能连,公网不能默认只绑定回环ss -lntp、ProxyJump
指定 0.0.0.0 仍只监听回环GatewayPorts nosshd -T
systemd 一直重启密钥、指纹、DNS 或端口冲突journalctl
运行几小时后失联NAT 状态过期或网络切换ServerAlive 参数
隧道端口存在但登录失败内网 sshd 或目标用户问题跳板机本机 ssh -p
手工命令成功,服务失败用户、HOME、known_hosts 不同User= 与绝对路径
重启 sshd 后所有隧道断开配置重载或服务重启使用 reload 并验证

十三、安全边界

13.1 反向隧道不是绕过管理制度的工具

公司、学校和生产网络可能明确禁止私建出口隧道。

部署前需要确认:

  • 网络和信息安全政策;
  • 数据是否允许经过公网服务器;
  • 跳板机是否经过授权;
  • 操作日志是否满足审计;
  • 设备是否属于个人可管理资产。

技术上能建立,不代表组织上允许。

13.2 跳板机本身要加固

至少做到:

禁用密码登录
管理员密钥分离
及时更新 OpenSSH
限制来源地址
只开放必要端口
启用登录审计
备份 sshd 配置

反向隧道把内网设备的管理入口延伸到了跳板机。

跳板机失守,所有隧道都可能成为攻击路径。

13.3 不要转发数据库到公网

类似:

-R 0.0.0.0:3306:127.0.0.1:3306

风险很高。

数据库管理优先通过:

SSH 登录设备后本地操作
回环监听 + ProxyJump
临时本地转发

避免长期公网暴露。


十四、健康检查

进程存活只能证明 SSH 客户端还在。

可以从跳板机定期检查:

timeout 5 \
  ssh \
  -o BatchMode=yes \
  -o ConnectTimeout=3 \
  -p 22022 \
  [email protected] \
  true

或只检查 TCP:

nc -z 127.0.0.1 22022

TCP 检查只能证明监听存在,不能证明最终认证可用。

更完整的健康指标:

systemd 当前状态
最近一次成功建立时间
重启次数
跳板机监听端口
端到端 SSH 测试
内网设备 sshd 状态

十五、上线前检查单

[ ] 内网设备本机 SSH 已验证
[ ] 隧道使用专用密钥
[ ] 跳板机使用专用账号
[ ] 默认只绑定跳板机回环地址
[ ] 外部连接使用 ProxyJump
[ ] 必须公网绑定时限制来源 IP
[ ] 已启用 ExitOnForwardFailure
[ ] 已配置 ServerAliveInterval 和 CountMax
[ ] systemd 使用绝对路径和 BatchMode
[ ] known_hosts 已核对,不关闭主机校验
[ ] sshd_config 修改前执行 sshd -t
[ ] 专用账号不能执行任意命令
[ ] 每台设备有独立密钥或账号
[ ] 有端到端健康检查
[ ] 已确认组织网络安全政策允许

总结

反向 SSH 解决的是连接方向问题:

内网设备不能被主动访问
但它可以主动连接公网跳板机

稳定运行要同时满足四层条件:

  1. 内网设备的 SSH 服务正常;
  2. 到跳板机的主连接可保持;
  3. 远端端口确实成功绑定;
  4. 外部客户端使用正确的入口访问。

推荐把反向端口只绑定在跳板机 127.0.0.1,再通过 ProxyJump 登录。这样不必额外暴露公网端口。

systemd 负责重启进程,保活参数负责发现假连接,ExitOnForwardFailure 负责发现端口根本没建起来。三者解决的是不同问题,不能只写一个 Restart=always 就认为链路可靠。

最后,给隧道账号、密钥、监听端口和可转发目标都设边界。反向 SSH 很方便,也很容易把一个临时维护入口变成长期暴露面。

资源分享
【Linux】系统彻底进不去怎么办?initramfs、recovery 与 chroot 救援指南
【Linux】内核升级后启动失败?旧内核回退、initramfs 重建与 DKMS 修复
【Linux排障实战】Docker容器启动失败怎么查:端口、日志、权限与网络

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2402_87731470/article/details/161982260

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--