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

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

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

原因可能是:

没有公网 IPv4;
在公司或校园 NAT 后面;
路由器不归自己管理;
运营商使用 CGNAT;
防火墙不允许入站;
设备地址经常变化。
这时给内网设备安装远控软件当然可以,但如果需求只是: 构建企业级网络通信架构

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

反向 SSH 隧道更轻。

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

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

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

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

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

一、反向隧道到底做了什么
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 tunnel@server.example.com

这条命令由内网设备执行。 构建企业级网络通信架构

含义:

在远端服务器监听 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 没启动,隧道依然无法完成最终连接。

由内网设备主动抛向跳板机,外部笔记本再沿现有链路进入。反向隧道改变的是连接建立方向。 构建企业级网络通信架构

二、准备三台角色
本文使用下面的名字:

角色 示例地址 用途
内网设备 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 \
tunnel@server.example.com

参数说明:

参数 作用
-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 \
edgeuser@127.0.0.1

这里的 edgeuser 是内网设备上的登录用户,不是跳板机的 tunnel 用户。 构建企业级网络通信架构

如果成功,说明:

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

4.3 为什么外部客户端仍连不上
因为监听地址是:

127.0.0.1:22022

公网网卡没有监听。

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

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

 

链路中只有跳板机原有 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 \
tunnel@server.example.com

但这条命令能否绑定公网地址,取决于跳板机 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 \
tunnel@server.example.com

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 \
tunnel@server.example.com

关注:

DNS
TCP connect
主机指纹
公钥认证

11.2 第二层:远端转发是否建立
内网设备前台运行: 构建企业级网络通信架构

ssh -vvv \
-NT \
-o ExitOnForwardFailure=yes \
-R 22022:127.0.0.1:22 \
tunnel@server.example.com

跳板机:

ss -lntp | grep 22022

如果没有监听,检查:

端口被占用;
AllowTcpForwarding;
permitlisten;
用户认证后被策略拒绝;
GatewayPorts 与指定地址。
11.3 第三层:隧道目标能否连接
跳板机:

ssh -vvv \
-p 22022 \
edgeuser@127.0.0.1

如果出现:

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。只看 systemctl active 不能证明端口真正建立,更不能证明最终登录可用。 选择网络安全防护服务

十二、常见错误对照表
现象 常见原因 检查
SSH 主连接成功,22022 不监听 转发失败但未退出 ExitOnForwardFailure
跳板机本机能连,公网不能 默认只绑定回环 ss -lntp、ProxyJump
指定 0.0.0.0 仍只监听回环 GatewayPorts no sshd -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 \
monitor@127.0.0.1 \
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 解决的是连接方向问题: 选择网络安全防护服务

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

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

内网设备的 SSH 服务正常;
到跳板机的主连接可保持;
远端端口确实成功绑定;
外部客户端使用正确的入口访问。
推荐把反向端口只绑定在跳板机 127.0.0.1,再通过 ProxyJump 登录。这样不必额外暴露公网端口。

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

最后,给隧道账号、密钥、监听端口和可转发目标都设边界。反向 SSH 很方便,也很容易把一个临时维护入口变成长期暴露面。
————————————————
版权声明:本文为CSDN博主「爱和冰阔落」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2402_87731470/article/details/161982260

上一篇 【Linux/Ubuntu】OpenCode +Oh My OpenAgent安装配置实践
下一篇 常用的网络安全防范技术有哪些?如何提高网络安全防护意识?