SSH ProxyJump:通过跳板机访问内网服务器

有些服务器不能从办公电脑直接访问,但可以先登录一台跳板机,再由跳板机访问内网目标服务器。

最直接的做法是先登录跳板机,再在跳板机上执行第二次 ssh。不过 SSH 自己就支持这条链路,使用 -JProxyJump)可以把两次连接写成一条命令。

假设网络关系如下:

当前电脑
    └── SSH ──> 跳板机 192.0.2.10:22022
                    └── SSH ──> 目标服务器 198.51.100.20:22

本文中的账号和地址都是脱敏示例:跳板机使用 jump-user@192.0.2.10:22022,目标服务器使用 target-user@198.51.100.20。请替换为实际环境的值。

当前电脑不能直接访问 198.51.100.20,但跳板机可以访问它。连接目标服务器只需要:

ssh -J jump-user@192.0.2.10:22022 target-user@198.51.100.20

-J 到底做了什么

这条命令中有两个 SSH 地址:

ssh -J jump-user@192.0.2.10:22022 \
    target-user@198.51.100.20

它们的角色不同:

  • -J jump-user@192.0.2.10:22022:先连接跳板机,用户名是 jump-user,SSH 端口是 22022
  • target-user@198.51.100.20:最终要登录的目标服务器,未指定端口时使用默认的 22

-J 的格式是:

-J [跳板机用户@]跳板机地址[:跳板机端口]

如果目标服务器的 SSH 端口不是 22,使用 -p 指定目标端口:

ssh -J jump-user@192.0.2.10:22022 \
    -p 2222 \
    target-user@198.51.100.20

这里的 -p 2222 属于目标服务器;跳板机的端口仍然写在 -J 后面。

连接过程

执行命令后,本地 SSH 客户端会做两件事:

  1. 先使用本地的密钥或密码认证跳板机
  2. 请求跳板机上的 sshd 建立一条到 198.51.100.20:22 的 TCP 通道

随后,本地 SSH 客户端通过这条通道和目标服务器完成真正的 SSH 握手与认证。

完整链路可以画成:

本地 ssh 客户端
    │
    │ 连接跳板机 192.0.2.10:22022
    ▼
跳板机上的 sshd
    │
    │ 连接 198.51.100.20:22
    ▼
目标服务器上的 sshd

跳板机的作用是转发 TCP 字节流,不需要在跳板机上再启动一个交互式 shell。目标服务器的私钥认证也由当前电脑完成,不需要把目标服务器的私钥复制到跳板机上。

这和下面这种手动操作不同:

ssh -p 22022 jump-user@192.0.2.10
ssh target-user@198.51.100.20

手动操作需要先进入跳板机,再从跳板机发起第二次连接;-J 则把跳板机隐藏成目标连接的代理通道。

两台机器使用不同的账号和密钥

跳板机和目标服务器不一定使用同一个账号,也不一定使用同一把密钥。

例如跳板机使用 jump-key,目标服务器使用 target-key。更推荐写入 ~/.ssh/config,分别为两个主机配置认证信息。这样每把密钥的作用范围都很清楚。

Host my-jump
    HostName 192.0.2.10
    User jump-user
    Port 22022
    IdentityFile ~/.ssh/jump-key

Host my-target
    HostName 198.51.100.20
    User target-user
    IdentityFile ~/.ssh/target-key
    ProxyJump my-jump

之后直接执行:

ssh my-target

配置文件还有一个好处:目标服务器地址、跳板机端口和密钥路径只需要维护一次,后续执行 scpsftp 或其他 SSH 工具时也可以复用。

例如使用 scp

scp ./example.txt my-target:/tmp/

多级跳板机

如果目标服务器前面还有多层网络隔离,可以在 -J 中用逗号分隔多个跳板机:

ssh -J user1@jump-1:22022,user2@jump-2:22 \
    user3@198.51.100.20

连接顺序是:

当前电脑 -> jump-1 -> jump-2 -> 198.51.100.20

也可以写成配置:

Host my-target
    HostName 198.51.100.20
    User user3
    ProxyJump user1@jump-1:22022,user2@jump-2:22

每增加一层跳板,排查网络问题就会多一层依赖,因此实际使用时应该尽量保持链路简单。

连接失败时怎么排查

先确认跳板机本身可以登录:

ssh -p 22022 jump-user@192.0.2.10

再从跳板机检查它能否访问目标服务器的 SSH 端口:

nc -vz 198.51.100.20 22

如果跳板机没有 nc,也可以直接在跳板机上尝试:

ssh -v target-user@198.51.100.20

最后在本地打开详细日志:

ssh -vv \
    -J jump-user@192.0.2.10:22022 \
    target-user@198.51.100.20

通常可以根据日志快速区分问题发生在哪一段:

  • 跳板机认证失败:检查跳板机用户名、端口和密钥
  • 跳板机无法连接目标端口:检查目标服务器路由、防火墙和 sshd
  • 目标服务器认证失败:检查目标服务器的用户、密钥和 authorized_keys
  • 出现 host key 警告:确认目标 IP 没有被重新分配给另一台机器

ProxyJump 的目标连接仍然是端到端的,因此第一次连接目标服务器时,当前电脑仍然可能看到目标服务器的 host key 确认提示。

和端口转发有什么区别

-J 解决的是“登录目标服务器”:

本地电脑 -> 跳板机 -> 目标服务器

登录成功后,得到的是目标服务器上的 shell。

如果只是想访问目标服务器上的某个 HTTP、数据库或其他 TCP 服务,而不需要登录 shell,应该使用 -L 本地端口转发。另一篇文章中的用法就是让本地浏览器访问内网 Web 服务:

本地程序 -> 本地端口 -> SSH 隧道 -> 目标服务器上的服务

两者可以同时使用:-J 负责“怎么到达目标服务器”,-L 负责“到达目标服务器后再转发哪个服务端口”。

安全边界

跳板机不是认证绕过。它只改变了到达目标服务器的网络路径,目标服务器仍然会独立校验用户和密钥。

实际部署时需要注意:

  • 跳板机必须允许连接目标服务器的 SSH 端口
  • 目标服务器的 host key 应该在当前电脑上验证并保存
  • 尽量使用密钥认证,避免把目标服务器私钥放到跳板机
  • 只给跳板机开放必要的目标网段和端口
  • 不要把跳板机当成无边界的通用代理

ProxyJump 的价值在于把“必须经过某台机器才能到达”的网络事实写进 SSH 命令或配置文件。命令本身很短,但它把多段连接、认证边界和最终目标区分得很清楚。