RustDesk 自建服务器 运维与诊断文档
🟡 部分可用 · 待重测
基础设施 Runbook

{{H1}}

Infrastructure Runbook · Oracle Cloud 新加坡

记录自建 RustDesk 远程桌面服务器的架构、配置、问题诊断过程与结论,供 AI 快速读懂上下文、本人长期维护迭代。

创建时间 2026-06-13最后更新 2026-07-05服务器 Oracle Cloud · 新加坡当前状态 🟡 部分可用 · 待重测
⚠️
当前状态:四台机器已全部换为电信宽带(2026-07-05,联通线路退网)

所有连接均为电信→电信,旧「电信→联通 ISP 封锁」结论作废;已验证直连:C→D(rust-d.<主域名>:21118);A/C 的 IPv6 已因换网变更(A 由 DDNS 自动跟踪,C 待装 DDNS);B→A、D→C、D→B 待重测(预期可通,见待办 ㉑);直连模式不消耗 Oracle 流量;中继可用(注意 Screen Inception,需先退出 A 上的 ToDesk);信令 / 中继服务器已切换为域名 rd.<主域名>

0文档维护说明(给未来的我 / AI)

  • 每次改动配置或排查,请在文末 「变更与迭代日志」 追加一条,并更新顶部「最后更新」与「当前状态」。
  • 状态图标约定:🟢 正常 🟡 部分可用 · 待优化 🔴 故障
  • 命令、IP、端口等关键信息以代码块记录,方便复制和 AI 解析。
  • 本文档为脱敏版:服务器 IP、IPv6 地址、设备 ID 均以占位符呈现,真实值在本机 private/secrets.json

1架构概览

项目内容
软件RustDesk(开源远程桌面,自建中继 / 信令服务器)
服务器Oracle Cloud 免费实例,地域:新加坡
系统Ubuntu(实例名 Oracle 实例名,登录用户 ubuntu
服务进程hbbs(信令 / ID Server)、hbbr(中继 / Relay Server)
主控端本人电脑,家庭宽带 1000M(中国,普通家宽)
被控端⚠️ 待补充(在哪条网络尚未确认)
连接现状加密中继连接 (TCP) —— 即走 Relay,未实现 P2P 直连

数据路径示意

理想(直连 / Direct)
主控本机
P2P 直连◀▶
被控远端机器

服务器仅做信令,画质好

现状(中继 / Relay)
主控本机
上行
新加坡服务器hbbr Relay
下行
被控远端机器

国际绕路,又卡又糊

2原始诉求(问题)

  1. 界面模糊、卡顿 —— 原因是什么,如何解决。
  2. 用二级域名代替 IP —— 是否更好、怎么配。

3诊断结论速览(TL;DR)

卡顿 / 模糊根因

Root cause

连接走了 Relay 中继,所有画面流量绕道新加坡服务器(免费实例带宽小 + 国内↔新加坡国际链路瓶颈)。

服务器侧已 100% 排除

Server-side · OK

防火墙端口、服务进程、UDP 监听全部正常。

真正卡点

NAT traversal

NAT 打洞失败,两端大概率都是 CGNAT 大内网(中国家宽通病),UDP 打不通 → 被迫退回中继。

1000M 宽带无用武之地

Bandwidth

走中继时瓶颈在服务器出口和国际链路,本地带宽再大也没用。

域名问题

DNS

建议用域名,但只提升「运维便利性」,不会改善画质 / 速度

4已验证的服务器配置(均正常 ✅)

4.1 UFW 防火墙(端口已全部放通)

ℹ️

实例用的是 UFW,不是 iptables。Oracle 云控制台安全列表已设为 all。

21115:21119/tcp   ALLOW IN   Anywhere      ✅
21116/udp         ALLOW IN   Anywhere      ✅ 打洞关键端口
(v6 规则同样已放通)
  • 另有 Fail2Ban 自动封禁大量恶意 SSH IP(正常防护,无需处理)。
  • 已开放的其他端口:22 (SSH)、80、443、8633 (tcp/udp);2096 为 DENY。

4.2 服务进程与监听

sudo ss -tulnp | grep 2111

udp  *:21116  hbbs (pid 3954199)   ✅ 打洞 UDP 在监听
tcp  *:21115  hbbs
tcp  *:21116  hbbs
tcp  *:21117  hbbr
tcp  *:21118  hbbs
tcp  *:21119  hbbr

结论:服务端完全正常,无需再动。

4.3 端口职责对照表

端口协议进程作用
21115TCPhbbsNAT 类型测试
21116TCP + UDPhbbs21116/UDP = NAT 打洞核心;TCP 为信令心跳
21117TCPhbbr中继 Relay
21118TCPhbbsWeb 客户端支持
21119TCPhbbrWeb 客户端中继

5已解决:IPv6 直连 ✅ 2026-06-13

根因确认

服务器侧无误,打洞失败根本原因是双端均为 CGNAT(IPv4 层),但两端均有公网 IPv6,IPv6 无 NAT,可直接 P2P。

各端 IPv6 信息

公网 IPv6运营商
A 机A 机 IPv6
2026-07-05 换电信后本机实测;rust-a.<主域名> DDNS 自动跟踪,原联通地址已失效
中国电信(与 D 同 /64 前缀,疑同一条宽带)
B 机B 机 IPv6中国电信
C 机待查
原联通地址已失效;换电信后未记录,待装 DDNS 时更新
中国电信
D 机与 A 同 /64 前缀
最新地址以 rust-d.<主域名> AAAA 记录为准,DDNS 自动更新
中国电信
服务器Oracle 服务器 IPv6Oracle 新加坡

当前生效的连接方式(域名直连,双向互控)

两台机器均可互为主控 / 被控,连接时填域名即可,IP 变化自动跟踪。

方向连接栏填写
A 机控制 B 机rust-b.<主域名>:21118
B 机控制 A 机rust-a.<主域名>:21118
A 机控制 C 机⚠️ 待重配 原联通临时 IP 已失效,待 C 装 DDNS 后改用 rust-c.<主域名>:21118
C 机控制 D 机 rust-d.<主域名>:21118

各端配置详情

A 机 电信

与 D 同 /64 前缀
  • 2026-07-05 由联通换为电信
  • 域名:rust-a.<主域名>(AAAA 自动更新)
  • 设置 → 安全 → 「允许 IP 直接访问」 ✅ 端口 21118
  • 防火墙入站 TCP 21118 已放行
  • DDNS 脚本:C:\Scripts\update-rustdesk-ipv6.ps1
  • 计划任务:RustDesk-DDNS-Boot(开机)+ RustDesk-DDNS-Repeat每 1 小时

B 机 电信

独立线路
  • 域名:rust-b.<主域名>(AAAA 自动更新)
  • 设置 → 安全 → 「允许 IP 直接访问」 ✅ 端口 21118
  • 防火墙入站 TCP 21118 已放行
  • DDNS 脚本:C:\Scripts\update-rustdesk-ipv6.ps1
  • 计划任务:Boot + Repeat(每 1 小时
  • Cloudflare API Token 名称:DDNS Token 名称(DNS Write,无过期)
  • RustDesk 服务:✅ 已安装(RustDesk Service Running)
  • RustDesk IDB 机 RustDesk ID(2026-06-14 重置后的新 ID)
  • ⚠️ 永久密码已重置RustDesk.toml 被删除,需重新在 B 的设置里设置永久密码

D 机 电信

与 B 不同线路
  • 域名:rust-d.<主域名>(AAAA 由脚本自动创建并更新)
  • 设置 → 安全 → 「允许 IP 直接访问」 ✅ 端口 21118
  • 防火墙入站 TCP 21118 已放行
  • DDNS 脚本:与 A/B 同款,仅 $SUBDOMAIN 改为 rust-d.<主域名>
  • 计划任务:Boot + Repeat(每 1 小时;改间隔命令见待办 ⑮)
  • 已验证:C→D 域名直连 ✅(2026-07-05)

DDNS 日志查看

Get-Content C:\Scripts\ddns.log -Tail 10

5.1已确认:B→A 直连单向失败 2026-06-14 · 历史

⚠️
2026-07-05 更新:本节结论已过时

当时 A 为联通,失败根因是电信→联通跨运营商封锁;现 A 已换为电信宽带,该前提不复存在,B→A 需按电信→电信重测(见待办 ㉑)。下文保留作历史记录。

现象

方向结果
A(联通)→ B(电信)直连✅ 成功 P2P 直连
B(电信)→ A(联通)直连❌ 失败 连接不通

结论

疑为电信侧单向封锁:电信 IPv6 出方向对联通 21118 端口有过滤或运营商 ACL 拦截,但联通→电信方向正常。此问题在用户侧无法修复。

当前 B→A 的可用替代方案

  1. 让 A 主动连 B(A→B 直连正常)—— 需要在 A 端操作,不能从 B 发起。
  2. B→A 走中继(Relay 模式)—— 可用,但有新 Bug,见 5.2。

5.2已定位:中继时画面套娃(Screen Inception)根因 ✅ 2026-06-14

复现路径

  1. ToDesk 远程连入 B(电信)
  2. 在 B 上打开 RustDesk,用 A 的 RustDesk ID 中继连接 A(联通)
  3. 连接成功后,画面出现多层叠加任务栏,无限嵌套闪烁

根因(已截图确认)

屏幕捕获递归(Screen Inception),不是 Bug,是物理规律:

B 的 RustDesk 捕获 A 的屏幕 └── A 的屏幕上有 ToDesk 窗口,正在显示 B 的屏幕 └── B 的屏幕里有 RustDesk 显示 A 的屏幕 └── 无限嵌套 → 多层任务栏叠加

A 上的 ToDesk 是循环的关键节点。只要 A 有 ToDesk 会话开着,B 通过 RustDesk 看到的 A 就会包含 B 自己的内容。

解决方案

连接前在 A 上关掉 ToDesk(退出,不是最小化)

  1. 通过 B 的 RustDesk 窗口点进 A 的桌面
  2. 右键 A 任务栏托盘中的 ToDesk 图标 → 退出
  3. 或 Ctrl + Alt + Del → 任务管理器 → 结束 ToDesk 进程
  4. ToDesk 关闭后循环立即断开,画面稳定

正确工作流(B 控制 A,无套娃)

  1. 物理到 B 旁边(或通过不涉及 A 屏幕的方式控制 B)
  2. 确认 A 上 ToDesk 已退出(无活跃会话)
  3. 在 B 的 RustDesk 里输入 A 的 ID(A 机 RustDesk ID)→ 连接
  4. 画面正常,无套娃
⚠️

待 B 旁边时验证,目前尚未实地测试(2026-06-14)。

6客户端画质优化(无论直连 / 中继都先做)

参考 RustDesk 远程窗口顶部工具栏菜单:

设置项当前建议
缩放适应窗口改为 原始尺寸(消除拉伸模糊)
画质平衡(默认)高清 或自定义拉高码率
编解码硬件编码 H264 / H265(两端需支持)
显示质量监控未勾勾选,实时看码率 / 帧率 / 延迟

7信令 / 中继服务器域名化 ✅ 2026-06-14 已完成

💡
结论:推荐用域名,但仅提升运维便利,不影响性能

✅ 好处:IP 变了只改一条 DNS,不用动所有客户端;好记好分发;便于后续上 TLS / 反代。 ❌ 不会让画面变快或变清晰(DNS 解析开销可忽略)。

已生效配置 · Cloudflare DNS 记录

TypeNameIPv4ProxyTTL
ArdOracle 服务器 IP灰云 DNS onlyAuto

必须灰云,橙色代理会拦截 21116-21119 端口。

A 机 + B 机 RustDesk 配置文件(已批量替换)

配置文件路径:%APPDATA%\RustDesk\config\RustDesk2.toml

# 执行命令(A 机和 B 机各执行一次,管理员 PowerShell)
$config = "$env:APPDATA\RustDesk\config\RustDesk2.toml"
(Get-Content $config) `
  -replace "rendezvous_server = '<旧IP>:21116'", "rendezvous_server = 'rd.<主域名>:21116'" `
  -replace "relay-server = '<旧IP>'", "relay-server = 'rd.<主域名>'" `
  -replace "custom-rendezvous-server = '<旧IP>'", "custom-rendezvous-server = 'rd.<主域名>'" |
  Set-Content $config

Restart-Service RustDesk

验证:

Get-Content "$env:APPDATA\RustDesk\config\RustDesk2.toml" | Select-String "server"
# 三行均应显示 rd.<主域名>,RustDesk 界面底部状态显示「就绪」

7.1服务器 Key 轮换标准流程 ✅ 2026-07-05 已验证

🔑

RustDesk 的 Key = hbbs 自动生成的 ed25519 密钥对(id_ed25519 私钥 + id_ed25519.pub 公钥),客户端填的是公钥内容。 本服务器为 Docker 部署:容器 hbbs / hbbr(镜像 rustdesk/rustdesk-server:latest),数据卷 /home/ubuntu/rustdesk/data → 容器内 /root,密钥和 ID 数据库(db_v2.sqlite3)均持久化在宿主机该目录,重建容器不丢数据。

服务器端(SSH 到实例)

# ① 备份并删除旧密钥(在宿主机数据卷目录操作)
cd /home/ubuntu/rustdesk/data
sudo mv id_ed25519 id_ed25519.bak
sudo mv id_ed25519.pub id_ed25519.pub.bak

# ② 重启容器,hbbs 发现无密钥会自动生成新的一对
sudo docker restart hbbs hbbr

# ③ 获取新公钥(这一串就是发给客户端的新 Key,44 字符左右,以 = 结尾)
sudo cat /home/ubuntu/rustdesk/data/id_ed25519.pub

# ④ 验证服务正常:应有 6 行监听(21115-21119 TCP + 21116 UDP)
sudo ss -tulnp | grep 2111
💡
回滚方法

.bak 两个文件改回原名,再 sudo docker restart hbbs hbbr

客户端(A / B / C / D 四台都要更新)

否则通过 ID 的信令 / 中继连不上。

方式一(图形界面):RustDesk → 设置 → 网络 → 解锁 → Key 栏粘贴新公钥(ID 服务器保持 rd.<主域名>)。

方式二(管理员 PowerShell 批量替换)

$config = "$env:APPDATA\RustDesk\config\RustDesk2.toml"
(Get-Content $config) -replace "key = '.*'", "key = '<新公钥>'" | Set-Content $config
Restart-Service RustDesk

验证:每台 RustDesk 主界面底部显示「就绪」。

影响范围

是否受影响路径
✅ 受影响通过 ID 连接的信令 / 中继路径(如 B→A 中继)—— 客户端未更新 Key 前连不上
❌ 不受影响IPv6 域名直连rust-x.<主域名>:21118)不经过 hbbs / hbbr,Key 变更无感

8关键信息速查

🔒

本节所有敏感值均为占位符。需要真实值时在本机执行 python build.py --private

服务器地域新加坡(Oracle Cloud 免费实例)
服务器公网 IPOracle 服务器 IP
服务器公网 IPv6Oracle 服务器 IPv6
信令 / 中继域名rd.<主域名>(Cloudflare A 记录,灰云 DNS only)
A 机直连域名rust-a.<主域名>(AAAA 记录,DDNS 自动更新)
B 机直连域名rust-b.<主域名>(AAAA 记录,DDNS 自动更新)
D 机直连域名rust-d.<主域名>(AAAA 记录,DDNS 自动更新)
SSH 登录ubuntu@Oracle 实例名
RustDesk Key(公钥)不入档 2026-07-05 已轮换,取值见服务器 /home/ubuntu/rustdesk/data/id_ed25519.pub
A 机 RustDesk IDA 机 RustDesk ID
B 机 RustDesk IDB 机 RustDesk ID
部署方式Docker(容器 hbbs / hbbr,镜像 rustdesk/rustdesk-server:latest,数据卷 /home/ubuntu/rustdesk/data → /root
hbbs PID(参考)2504031(2026-07-05 换 Key 重启后)
hbbr PID(参考)2504115(2026-07-05 换 Key 重启后)

9变更与迭代日志

按时间升序记录,最新一条在最下方。

💡
维护提示

每次改动复制下面这行追加到上表: | YYYY-MM-DD | 做了什么改动 / 排查了什么 | 🟢/🟡/🔴 |

10待办事项

ℹ️

完成一项就把复选框勾上(- [ ] 改成 - [x]),并到第 9 节日志追加一行。 标记约定:[x] 已完成 · [ ] 待办 · [!] 阻塞 · [~] 已并入其他条目

🔴 当前阻塞:让连接从「中继」变「直连」

  • ① 确认被控端所在网络 —— 中国电信家宽,有公网 IPv6。
  • ② 两端各测 IPv6 —— 主控联通 / 被控电信 / 服务器 Oracle,三端均有公网 IPv6。
  • ③ IPv6 直连验证 —— 采用直接 IP 直连方式,连接成功,P2P 直连实现。
  • ⑤ 验收 —— 直连成功,不再走中继。
  • ⑨ IPv6 DDNS 稳定化 —— 双端均已配置 Cloudflare DDNS + Windows 计划任务,连接改用域名,IP 变化自动跟踪。

🟡 优化项(直连成功后或并行做)

  • ⑥ 客户端画质 —— 缩放改「原始尺寸」+ 画质「高清」+ 开硬件编码 H264/H265 + 勾「显示质量监控」。
  • ⑦ 二级域名 —— rd.<主域名> A 记录已建(灰云),A/B 机 RustDesk2.toml 已全部替换,验证通过。

🖥️ 扩展:C 机和 D 机接入

  • ⑩ 确认 C / D 机网络 —— C 机联通(IPv6 已记录);D 机电信(非 B 机)。
  • ⑪ 确认子域名命名 —— rust-c.<主域名> / rust-d.<主域名>
  • ⑫ C 机装 DDNS —— ⚠️ C 已换电信,IPv6 已变,原联通地址和「A→C 已验证」均失效。装 DDNS(脚本同 A/B/D,$SUBDOMAINrust-c.<主域名>)+ 计划任务后,连接改用 rust-c.<主域名>:21118 并重测。
  • ⑬ D 机装 DDNS —— 已完成(2026-07-05):DDNS 脚本 + 计划任务已装,C 机用 rust-d.<主域名>:21118 域名直连验证成功。
  • !㉑ 全电信后重测各方向直连 —— 四台已全为电信,旧跨运营商封锁结论作废,预期两两可通:B→A(填 rust-a.<主域名>:21118)、D→C(待 C 装 DDNS 后填 rust-c.<主域名>:21118)、D→B(填 rust-b.<主域名>:21118,即原 ⑲)。全部通过后 ⑭ 验收即可关闭。
  • ⑭ 验收 —— 四台机器两两连接测试,确认均为直连 P2P。
  • ⑲ 测试电信→电信直连(D→B) —— 已并入 ㉑(四台全电信后统一重测)。

🗑️ 可以清理的任务

  • ⑳ 删除 A 机的 DDNS 定时任务 —— A 机永远只作主控端,不需要被远程控制。可删除 RustDesk-DDNS-BootRustDesk-DDNS-Repeat 两个计划任务及 DDNS 脚本。B 机的同名任务必须保留(A 连 B 依赖它)。⚠️ 2026-07-05:建议暂缓 —— A 换电信后 B→A 直连可能恢复(㉑ 待测),若测通且需要反向控制 A,则 A 的 DDNS 必须保留。

🔧 可选优化

  • ⑮ 调整 DDNS 检测间隔为 1 小时(IPv6 地址基本不变,1 小时完全够用,已执行)。
# A 机和 B 机各执行一次(/ri 单位:分钟)
schtasks /change /tn "RustDesk-DDNS-Repeat" /ri 60

改为更长间隔(一天 / 一周 / 一月)schtasks /change 改不了调度类型(/sc),需用 /f 覆盖重建任务。在目标机器管理员 PowerShell 执行下面其中一条即可:

# 每天一次(每天 09:00)
schtasks /create /tn "RustDesk-DDNS-Repeat" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\update-rustdesk-ipv6.ps1" /sc daily /mo 1 /st 09:00 /ru SYSTEM /rl HIGHEST /f

# 每周一次(每周一 09:00)
schtasks /create /tn "RustDesk-DDNS-Repeat" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\update-rustdesk-ipv6.ps1" /sc weekly /d MON /st 09:00 /ru SYSTEM /rl HIGHEST /f

# 每月一次(每月 1 号 09:00)
schtasks /create /tn "RustDesk-DDNS-Repeat" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\update-rustdesk-ipv6.ps1" /sc monthly /mo 1 /d 1 /st 09:00 /ru SYSTEM /rl HIGHEST /f

# 改完验证
schtasks /query /tn "RustDesk-DDNS-Repeat" /v /fo list
⚠️

间隔越长,IPv6 变更后 DNS 失效的窗口越长(例如每月一次,IP 变了最坏要等一个月才自动修正)。好在 RustDesk-DDNS-Boot 开机必跑一次,重启机器即可立即修正;连不上时也可在被控端手动跑一次脚本。建议不低于每天一次

🔴 B→A 连接问题

  • ⑯ 排查 B→A 单向失败根因 —— 已确认:ISP 封锁(电信→联通 IPv6 路由不通),用户侧无法修复。
  • ⑰ 验证 Screen Inception 修复 —— 待实地到 B 旁边测试:连接前在 A 上退出 ToDesk,再从 B 用 A 的 ID 中继连 A,确认画面正常。
  • ⑱ 重设 B 的 RustDesk 永久密码 —— B 的 RustDesk.toml 被误删,密码已重置,需在 B 的 RustDesk → 设置 → 安全 → 重新设置永久密码。

📝 文档待补全

  • ⑧ 补全第 8 节速查表 —— 服务器公网 IP、域名、Key 等占位信息(Key 注意脱敏勿外泄)。