使用反向 SSH 隧道实现低延迟远程访问

跨地区访问内网服务器时,Tailscale、RustDesk 等工具通常会先尝试 UDP 打洞。打洞失败后,流量会转发到公共中继节点,连接虽然仍然可用,但路径不可控,延迟也可能明显增加。本文使用一台网络质量较好的 VPS,通过反向 SSH 隧道建立一条固定的 TCP 链路,使 SSH 和远程桌面流量都按照“本地设备 → VPS → 内网服务器”的路径传输。

实现原理

内网服务器通常不能接受公网入站连接,但可以主动访问互联网。我们让内网服务器主动连接 VPS,并通过 SSH 的 -R 参数在 VPS 上创建两个反向端口:127.0.0.1:22022 对应内网服务器的 SSH 端口 22127.0.0.1:23389 对应 RDP 端口 3389。SSH 连接建立后,VPS 收到的本地端口流量会通过已经存在的加密连接送回内网服务器。

1
2
3
4
ssh -NT \
  -R 127.0.0.1:22022:127.0.0.1:22 \
  -R 127.0.0.1:23389:127.0.0.1:3389 \
  tunnel@<VPS_IP>

其中,-N 表示不执行远程命令,-T 表示不分配终端,-R 用于建立反向端口转发。两个端口都绑定到 VPS 的 127.0.0.1,因此只能由 VPS 本机访问,不会直接暴露到公网。

配置专用用户

首先在 VPS 上创建一个只用于维持隧道的用户,并锁定其密码登录:

1
2
3
sudo useradd --create-home --shell /bin/bash tunnel
sudo passwd -l tunnel
sudo install -d -o tunnel -g tunnel -m 700 /home/tunnel/.ssh

然后在内网服务器生成一对专用 SSH 密钥:

1
2
3
4
5
6
sudo mkdir -p /root/.ssh
sudo chmod 700 /root/.ssh
sudo ssh-keygen -t ed25519 \
  -f /root/.ssh/id_ed25519_hk_tunnel \
  -N '' \
  -C 'lab-to-vps-tunnel'

将公钥写入 VPS 的 /home/tunnel/.ssh/authorized_keys。为了避免该密钥被用于其他操作,可以限制它只能建立指定的反向端口:

1
restrict,port-forwarding,permitlisten="127.0.0.1:22022",permitlisten="127.0.0.1:23389" ssh-ed25519 <PUBLIC_KEY>

最后修正文件权限:

1
2
sudo chown tunnel:tunnel /home/tunnel/.ssh/authorized_keys
sudo chmod 600 /home/tunnel/.ssh/authorized_keys

保持隧道在线

手动运行 SSH 只适合测试,长期使用可以把它交给 systemd 管理。在内网服务器创建 /etc/systemd/system/vps-tunnel.service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[Unit]
Description=Persistent reverse SSH tunnel to VPS
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/ssh -NT -i /root/.ssh/id_ed25519_hk_tunnel -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=20 -o ServerAliveCountMax=3 -R 127.0.0.1:22022:127.0.0.1:22 -R 127.0.0.1:23389:127.0.0.1:3389 tunnel@<VPS_IP>
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启用服务后,隧道会随系统启动,并在网络中断后自动重连:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable --now vps-tunnel.service
sudo systemctl status vps-tunnel.service

验证隧道

在 VPS 上检查监听端口:

1
2
3
ss -lnt | grep -E '22022|23389'
nc -vz 127.0.0.1 22022
nc -vz 127.0.0.1 23389

如果两个端口均显示为开放,说明 VPS 已经能够通过反向隧道访问内网服务器的 SSH 和 RDP 服务。

使用 SSH

本地设备可以使用 ProxyJump 登录 VPS,再从 VPS 的回环端口进入内网服务器:

1
ssh -J <VPS_USER>@<VPS_IP> -p 22022 <SERVER_USER>@127.0.0.1

为了方便日常使用,可以将下面的内容写入本地 ~/.ssh/config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Host hk-vps
    HostName <VPS_IP>
    User <VPS_USER>
    IdentityFile ~/.ssh/id_rsa

Host lab-server
    HostName 127.0.0.1
    Port 22022
    User <SERVER_USER>
    ProxyJump hk-vps
    HostKeyAlias lab-server-through-vps

Host lab-rdp
    HostName <VPS_IP>
    User <VPS_USER>
    IdentityFile ~/.ssh/id_rsa
    LocalForward 13389 127.0.0.1:23389
    ExitOnForwardFailure yes
    ServerAliveInterval 20
    ServerAliveCountMax 3

IdentityFile 指向本地设备上用于登录 VPS 的 SSH 私钥,而不是以 .pub 结尾的公钥。如果 ~/.ssh 中已有 id_rsaid_rsa.pub,这里应填写 ~/.ssh/id_rsa;如果使用 Ed25519 密钥,则填写 ~/.ssh/id_ed25519。首次使用前,需要把对应的公钥复制到 VPS:

1
ssh-copy-id -i ~/.ssh/id_rsa.pub <VPS_USER>@<VPS_IP>

如果 macOS 没有安装 ssh-copy-id,也可以执行:

1
cat ~/.ssh/id_rsa.pub | ssh <VPS_USER>@<VPS_IP> 'umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys'

完成后测试密钥登录:

1
ssh -i ~/.ssh/id_rsa <VPS_USER>@<VPS_IP>

如果不再要求输入 VPS 账号密码,说明密钥已经配置成功。私钥 id_rsa 只保存在本地,不能上传或发送给其他人;复制到 VPS 的只能是 id_rsa.pub

之后只需执行 ssh lab-server。这等价于 ssh -J <VPS_USER>@<VPS_IP> -p 22022 <SERVER_USER>@127.0.0.1,VS Code Remote-SSH、Cursor 和 SFTP 也可以直接使用 lab-server

使用远程桌面

RDP 端口没有暴露到公网,因此需要先在本地建立一层 SSH 本地转发。如果已经添加了上面的 lab-rdp 配置,执行:

1
ssh -NT lab-rdp

该命令会读取 LocalForward 13389 127.0.0.1:23389,等价于下面的完整命令:

1
2
3
4
ssh -NT \
  -o ServerAliveInterval=20 \
  -L 13389:127.0.0.1:23389 \
  <VPS_USER>@<VPS_IP>

保持终端运行,然后使用 RDP 客户端连接 127.0.0.1:13389。此时流量依次经过本地端口 13389、VPS 端口 23389 和内网服务器端口 3389,整个过程都位于 SSH 加密通道内。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy