通过 VPS 实现低延迟远程访问:SSH、Tailscale 与 RustDesk

实验室服务器位于严格 NAT 后,不能接受公网入站连接;深圳 Mac 与它之间也无法稳定完成 UDP 打洞。本文选择一台到深圳和实验室网络延迟都较低的 VPS,分别部署反向 SSH 隧道、Tailscale Peer Relay 和 RustDesk Relay。三套方案不互相依赖,可以同时运行。

命名

为避免混淆,全文固定使用以下名称:

名称设备作用
mac-client深圳 Mac;Tailscale 名称为 MBA发起 SSH 或远程桌面连接
relay-vps公网 VPS;Tailscale 名称为 hk-relay提供固定公网入口和中转
lab-server实验室服务器;Tailscale 名称为 lab-server最终访问目标

文中的占位符含义如下:

占位符含义获取方法
<RELAY_IP>relay-vps 的公网 IPv4 地址在 VPS 服务商控制台查看
<RELAY_USER>mac-client 登录 VPS 使用的 SSH 用户可以是 root 或普通管理用户;不等于专用隧道用户 tunnel
<LAB_USER>登录 lab-server 使用的 Linux 用户在目标端执行 whoami
<LAB_TS_IP>lab-server 的 Tailscale IPv4 地址在目标端执行 tailscale ip -4
<TUNNEL_PUBLIC_KEY>lab-server 为反向隧道生成的 SSH 公钥执行 sudo cat /root/.ssh/id_ed25519_vps_tunnel.pub
<RUSTDESK_KEY>RustDesk 服务端的公钥在 VPS 执行 sudo cat /opt/rustdesk/data/id_ed25519.pub;不要复制私钥 id_ed25519
<VNI_ID>Tailscale 为一次 Peer Relay 连接分配的标识自动生成,只用于识别输出,不需要手工填写

RustDesk 的“服务端”专指 VPS 上的 hbbshbbr,不是 lab-server

方案一:反向 SSH 隧道

这套方案只依赖 TCP。首先由 lab-server 主动连接 relay-vps,在 VPS 回环地址上建立两个反向端口:22022 → lab-server:2223389 → lab-server:3389。之后 mac-client 再通过 VPS 进入这条隧道。开始配置前,必须确认 lab-server 的 SSH 服务监听 22/tcp,RDP 服务监听 3389/tcp;不使用 RDP 时删除所有包含 233893389 的配置。

relay-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

lab-server 生成专用密钥并显示公钥:

1
2
3
sudo install -d -m 700 /root/.ssh
sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519_vps_tunnel -N ''
sudo cat /root/.ssh/id_ed25519_vps_tunnel.pub

以管理员身份把公钥写入 relay-vps/home/tunnel/.ssh/authorized_keys。下面的选项拒绝 shell 和远程命令,并把 -R 允许创建的监听地址限定为两个回环端口:

1
command="/bin/false",restrict,port-forwarding,permitlisten="127.0.0.1:22022",permitlisten="127.0.0.1:23389" ssh-ed25519 <TUNNEL_PUBLIC_KEY>
1
2
sudo chown tunnel:tunnel /home/tunnel/.ssh/authorized_keys
sudo chmod 600 /home/tunnel/.ssh/authorized_keys

在启动 systemd 服务前,先比较 relay-vps 本机显示的 Ed25519 指纹与 lab-server 扫描到的指纹。两者完全一致后,才把主机密钥写入 root 的 known_hosts

1
2
3
4
5
6
7
8
9
# relay-vps
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

# lab-server:先比较指纹,再写入 known_hosts
ssh-keyscan -t ed25519 <RELAY_IP> | ssh-keygen -lf -
sudo touch /root/.ssh/known_hosts
sudo ssh-keygen -f /root/.ssh/known_hosts -R <RELAY_IP>
sudo ssh-keyscan -H -t ed25519 <RELAY_IP> | sudo tee -a /root/.ssh/known_hosts
sudo chmod 600 /root/.ssh/known_hosts

lab-server 创建 /etc/systemd/system/relay-vps-tunnel.service

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

[Service]
Type=simple
User=root
ExecStart=/usr/bin/ssh -NT -i /root/.ssh/id_ed25519_vps_tunnel -o BatchMode=yes -o StrictHostKeyChecking=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@<RELAY_IP>
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
1
2
sudo systemctl daemon-reload
sudo systemctl enable --now relay-vps-tunnel.service

relay-vps 验证端口。两者必须监听在 127.0.0.1,不需要在防火墙中开放 2202223389

1
ss -lnt | grep -E '22022|23389'

mac-client~/.ssh/config 中写入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Host relay-vps
    HostName <RELAY_IP>
    User <RELAY_USER>

Host lab-server-via-ssh
    HostName 127.0.0.1
    Port 22022
    User <LAB_USER>
    ProxyJump relay-vps
    HostKeyAlias lab-server-through-relay-vps

Host lab-rdp-via-ssh
    HostName <RELAY_IP>
    User <RELAY_USER>
    LocalForward 13389 127.0.0.1:23389
    ExitOnForwardFailure yes
    ServerAliveInterval 20

mac-client 需要分别通过 <RELAY_USER><LAB_USER> 完成两次 SSH 认证。如果需要免密码登录,在反向隧道已经运行后,把 Mac 公钥分别加入两个账户。下面以 id_rsa.pub 为例;使用 Ed25519 密钥时改为 id_ed25519.pub。只能复制 .pub 公钥,私钥始终留在 Mac:

1
2
cat ~/.ssh/id_rsa.pub | ssh relay-vps 'umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys'
cat ~/.ssh/id_rsa.pub | ssh lab-server-via-ssh 'umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys'

~/.ssh/id_rsa~/.ssh/id_ed25519 都是 OpenSSH 默认尝试的私钥文件,因此不需要 IdentityFile;只有非默认路径的私钥才需要显式指定。SSH 登录执行 ssh lab-server-via-ssh。切换目标端用户可执行 ssh 其他用户名@lab-server-via-ssh,但该用户必须已在 lab-server 上创建,并已配置密码或 Mac 公钥。远程桌面先运行 ssh -NT lab-rdp-via-ssh,再让 RDP 客户端连接 127.0.0.1:13389

方案二:Tailscale Peer Relay

mac-clientrelay-vpslab-server 都安装 Tailscale,并登录同一个 tailnet。参与 Peer Relay 的版本必须不低于 1.86。本次 tailnet 中三个节点分别显示为 MBAhk-relaylab-server,可用 tailscale status 确认。

relay-vps 配置 Peer Relay,并确保配置端口与防火墙端口完全一致:

1
2
3
sudo tailscale set --relay-server-port=3478 \
  --relay-server-static-endpoints=<RELAY_IP>:3478
sudo ufw allow 3478/udp comment 'Tailscale Peer Relay'

在 Tailscale 管理后台的 Access controls 中,把下面的规则合并到现有 grants 数组:

1
2
3
4
5
6
7
{
  "src": ["lab-server"],
  "dst": ["hk-relay"],
  "app": {
    "tailscale.com/cap/relay": []
  }
}

这里的 src 是位于严格 NAT 后、需要通过中转访问的 lab-serverdst 才是提供中转的 relay-vps,即 tailnet 中的 hk-relay。在 mac-client 验证连接:

1
2
tailscale ping <LAB_TS_IP>
tailscale status | grep <LAB_TS_IP>

成功时会看到:

1
peer-relay <RELAY_IP>:3478:vni:<VNI_ID>

tailscale ping 的首个响应可以显示 DERP,因为客户端仍在协商路径;后续响应和 tailscale status 必须显示 peer-relay,否则 VPS 中转没有生效。direct connection not established 只表示点对点打洞失败。Peer Relay 只转发端到端加密的 Tailscale 流量,不能读取 SSH 或 RDP 内容。本文使用系统 OpenSSH,因此 SSH 命令是 ssh <LAB_USER>@<LAB_TS_IP>;切换 Linux 用户只需更改 <LAB_USER>。RDP 连接地址是 <LAB_TS_IP>:3389

方案三:RustDesk Relay

这套方案不依赖 Tailscale。VPS 上的 hbbs 负责 ID、注册和信令,hbbr 负责中转桌面流量;mac-clientlab-server 都是 RustDesk 客户端。

relay-vps 创建 /opt/rustdesk/compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
services:
  hbbs:
    container_name: hbbs
    image: rustdesk/rustdesk-server:latest
    command: hbbs
    environment:
      - ALWAYS_USE_RELAY=Y
    volumes:
      - ./data:/root
    network_mode: host
    restart: unless-stopped

  hbbr:
    container_name: hbbr
    image: rustdesk/rustdesk-server:latest
    command: hbbr -k _
    volumes:
      - ./data:/root
    network_mode: host
    restart: unless-stopped

ALWAYS_USE_RELAY=Y 强制会话经过 VPS,hbbr -k _ 使用同一密钥校验客户端,避免 VPS 成为开放中转。启动服务并放行原生客户端所需的最小端口:

1
2
3
4
5
6
7
8
sudo mkdir -p /opt/rustdesk/data
cd /opt/rustdesk
sudo docker compose up -d
sudo ufw allow 21115/tcp
sudo ufw allow 21116/tcp
sudo ufw allow 21116/udp
sudo ufw allow 21117/tcp
sudo cat data/id_ed25519.pub

21115/tcp 用于 NAT 测试,21116/tcp21116/udp 用于注册与信令,21117/tcp 承载中转流量。原生 RustDesk 客户端不需要 WebSocket 端口 2111821119。最后一条命令输出的内容就是 <RUSTDESK_KEY>。在 mac-client 的 RustDesk Settings → Network 中填写:

1
2
3
4
ID Server:    <RELAY_IP>
Relay Server: <RELAY_IP>
API Server:   留空
Key:          <RUSTDESK_KEY>

lab-server 的普通桌面安装可以填写同样的图形界面选项,并在 Settings → Security 中设置永久密码;未设置永久密码时,每次连接都需要目标端人工确认。本次部署使用 systemd 和 GDM 提供无人值守访问,有效配置源是 /root/.config/rustdesk/RustDesk2.toml,不能只修改普通用户目录。先停止服务并编辑该文件;只修改下面列出的字段,其余内容保持不变:

1
2
sudo systemctl stop rustdesk
sudoedit /root/.config/rustdesk/RustDesk2.toml
1
2
3
4
5
6
7
rendezvous_server = '<RELAY_IP>:21116'

[options]
custom-rendezvous-server = '<RELAY_IP>'
relay-server = '<RELAY_IP>'
key = '<RUSTDESK_KEY>'
allow-linux-headless = 'Y'

通过界面确认服务器公钥时,RustDesk 会自动记录确认状态。若完全手动配置,还要在 /root/.config/rustdesk/RustDesk.toml/var/lib/gdm3/.config/rustdesk/RustDesk.toml 已有的 [keys_confirmed] 段中都加入:

1
"<RELAY_IP>:21116" = true

然后重新启动目标端服务:

1
2
sudo systemctl enable --now rustdesk
sudo rustdesk --get-id

RustDesk2.toml 会从 root 配置同步给 GDM,但密钥确认记录应单独检查。启动后确认 /var/lib/gdm3/.config/rustdesk/RustDesk2.toml 中的 ID Server、Relay Server 和 Key 仍与 root 配置一致。在 mac-client 输入上一步得到的 RustDesk ID 发起连接,同时在 relay-vps 执行 docker logs -f hbbr;出现 got paired 表示桌面流量正在经过 VPS。ID Server 和 Relay Server 必须填写 VPS 公网地址,不能填写 VPS 的 Tailscale 地址,否则会变成 RustDesk over Tailscale。

选择

只需要 SSH、VS Code Remote-SSH 或 RDP,且 tailscale status 能稳定显示 peer-relay 时,使用 Tailscale。Tailscale 无法建立 Peer Relay,或者网络只能稳定传输 TCP 时,使用反向 SSH 隧道。需要完整桌面、文件传输或无人值守控制时,使用 RustDesk。保留反向 SSH 隧道,可在 Tailscale 或 RustDesk 故障时继续登录 lab-server

参考:Tailscale Peer RelayRustDesk 自建服务器

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