让 RustDesk 走我自己的服务器
商业远控的连接都要过一次别人的服务器。自建 hbbs + hbbr 能把这个环节拿回来,代价只是一条 docker compose。国内拉镜像和 latest 标签,是最容易卡住的两处。
TeamViewer、ToDesk、向日葵这类远控,连接都要过一次厂商的服务器。自建 RustDesk
服务端的意义在于把这个环节拿回来,代价是一条 `docker compose`。
它由两个组件组成,加起来不到 200 MB 内存。
以上是 AI 帮我写的,实际上,我用 RustDesk 的原因是: ChatGPT 力荐,我有一个ECS。
先决条件
| 需要 | 说明 |
|---|---|
| 一台有公网 IP 的服务器 | 客户端要能直接连上它,所以不能是纯内网机 |
| Docker + Docker Compose | 部署方式,两个容器 |
| 能改云服务商的安全组 | 端口要在安全组和服务器本机防火墙两处都开 |
| RustDesk 客户端 | Windows / macOS / Linux / Android / iOS 都有 |
三个自查命令:
```bashdocker --version # 有 Docker 就行 docker compose version # 注意是 compose 子命令,不是 docker-compose
```
这套做法在什么环境上验证过
```plaintext部署时间:2026-07-20 | 镜像 rustdesk/rustdesk-server:1.1.15 服务端:国内云服务器 | Docker Compose | network_mode: host 客户端:Linux (CachyOS) | 自定义 ID 服务器 + 中继 + 密钥
```
| 项 | 是否通用 | 说明 |
|---|---|---|
| hbbs / hbbr 的职责与端口 | ✅ 通用 | RustDesk 自身设计 |
| compose 文件结构 | ✅ 通用 | 与云厂商无关 |
| 国内拉镜像 | ⚠️ 只对国内服务器 | 有外网或已配加速器的可以跳过那一节 |
| 防火墙命令 | ⚠️ 因发行版而异 | `ufw` 还是 `firewalld`,先看自己那台 |
| 镜像版本号 | ❌ 会过时 | 写这篇时是 1.1.15,装之前去看一眼 Releases |
它由两个组件组成
| 组件 | 容器 | 作用 |
|---|---|---|
| hbbs | `rustdesk-hbbs` |
设备注册、NAT 类型测试、P2P 连接协调 |
| hbbr | `rustdesk-hbbr` |
P2P 直连失败时,负责中继转发 |
```plaintextA 想连 B └─ 先问 hbbs:B 在哪?能直连吗? ├─ 能 ──→ 数据走 P2P,不经服务器 └─ 不能 ──→ 走 hbbr 中继 (对称 NAT 之类)
```
大多数时候流量是不经过服务器的,服务器只负责牵线。这也是它便宜的原因: 一台最低配的机器就够了,只有在中继时才吃带宽。
端口
| 端口 | 协议 | 归属 | 用途 |
|---|---|---|---|
| 21115 | TCP | hbbs | NAT 类型测试 |
| 21116 | TCP | hbbs | ID 注册与心跳 |
| 21116 | UDP | hbbs | 打洞 / NAT 穿透 |
| 21117 | TCP | hbbr | 中继服务 |
| 21118 | TCP | hbbs | WebSocket,可选 |
21115 TCP、21116 TCP + UDP、21117 TCP 四个必须开。
21116 那个 UDP 最容易漏。只开 TCP 的话,客户端能注册上线,但打洞会失败, 于是所有流量都被挤到中继上,速度明显不对。
第一步:写 compose 文件
```bashmkdir -p /opt/rustdesk-server && cd /opt/rustdesk-server
```
写入 `compose.yml`:
```yamlservices: hbbs: container_name: rustdesk-hbbs image: rustdesk/rustdesk-server:1.1.15 # 必须写具体版本号 command: hbbs -r <你的服务器IP>:21117 -k _ volumes: - ./data:/root network_mode: host restart: unless-stopped depends_on: - hbbr hbbr: container_name: rustdesk-hbbr image: rustdesk/rustdesk-server:1.1.15 # 同上 command: hbbr -k _ volumes: - ./data:/root network_mode: host restart: unless-stopped
```
参数逐个说:
| 参数 | 含义 |
|---|---|
`-r <IP>:21117` |
告诉客户端中继服务器在哪。填公网 IP |
`-k _` |
强制密钥认证。首次启动自动生成密钥对 |
`network_mode: host` |
直接用宿主机网络栈,端口不用再做映射 |
`./data:/root` |
持久化密钥和数据库。这个目录丢了,所有客户端都要重配 |
⚠️ 坑一:绝不能用 `latest`
注意,这篇文章的更新时间虽然是`
2026-9-20`,但实际上笔者在这一天并没有真正意义上去测试,所以这一条的时间线为`2026-07-20`
这是这篇里最值得记的一条。用 `latest` 拉到旧版本镜像后,容器起不来,日志里是:
```plaintextERROR: Registered email required (-m option). Please pay and register on https://rustdesk.com/server
```
看着像「要付费注册」,其实是国内某些镜像源缓存了几年前的旧版本, 而旧版本要求商业授权。写死版本号就能绕开。
装之前先去 Releases
看一眼当前版本,把 `1.1.15` 换成它。
⚠️ 坑二:国内服务器拉不动镜像
如果服务器在国内且没有代理,`docker compose pull` 会卡在 Docker Hub 上。两个办法:
办法一:直接用代理前缀拉,再打回原标签
```bashdocker pull docker.1ms.run/rustdesk/rustdesk-server:1.1.15 docker tag docker.1ms.run/rustdesk/rustdesk-server:1.1.15 \ rustdesk/rustdesk-server:1.1.15
```
改完标签,compose 文件里的 `image` 不用动。
办法二:给 Docker 配加速器,一劳永逸
```bashsudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<'EOF' { "registry-mirrors": [ "https://docker.1ms.run", "https://docker.xuanyuan.me", "https://docker.m.daocloud.io" ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker
```
配多个源是为了容灾,某一个挂了还有别的。之后 `docker compose pull` 就能直接拉。
这些镜像代理的可用性会变,用之前先自己试一下。写这篇时三个都还能用。
第二步:启动并拿到密钥
```bashcd /opt/rustdesk-server docker compose up -d # 拿公钥 cat ./data/id_ed25519.pub
```
密钥文件在 `./data/` 下,是首次启动时自动生成的。客户端要填的就是 `id_ed25519.pub`
里的那一串。
第三步:开端口
两处都要开,只开一处等于没开:
服务器本机防火墙
```bash# ufw(Debian / Ubuntu) sudo ufw allow 21115:21117/tcp sudo ufw allow 21116/udp sudo ufw reload
```
```bash# firewalld(RHEL / CentOS / Fedora) sudo firewall-cmd --permanent --add-port=21115-21117/tcp sudo firewall-cmd --permanent --add-port=21116/udp sudo firewall-cmd --reload
```
云服务商的安全组,在控制台里放行同样这几个端口。
第四步:配客户端
在 RustDesk 客户端里进 设置 → 网络,填三样:
| 字段 | 填什么 |
|---|---|
| ID 服务器 | 你的服务器 IP |
| 中继服务器 | 同一个 IP(端口不用写,默认 21117) |
| 密钥 | `id_ed25519.pub` 的内容 |
点应用,状态变绿显示「就绪」就是通了。
手机上也要各填一遍。 这几项不会跟着账号走,换设备就得重填。
怎么验证
① 服务端在不在
```bashdocker compose ps # 两个容器都应该是 Up docker logs rustdesk-hbbs # 没有报错
```
② 端口对外通不通
在另一台机器上跑,不要在服务器本机跑(本机跑测不出安全组):
```bashfor p in 21115 21116 21117; do timeout 3 bash -c "echo > /dev/tcp/<你的服务器IP>/$p" 2>/dev/null \ && echo "$p 开着" || echo "$p 不通" done
```
三个都应该是「开着」。
③ 客户端连上后看状态
RustDesk 主界面左下角,状态是「就绪」就说明 ID 服务器通了。
④ 真正连一次
从手机连一次电脑,看画面能不能出来。这一步才作数 —— 前面三项目前全绿、 最后连不上,通常是中继或者 UDP 打洞的问题。
排错
| 现象 | 可能原因 | 下一步 |
|---|---|---|
| 容器起不来,日志提「paid and register」 | 用了 `latest`,拉到旧版本 |
改成具体版本号重新拉 |
`docker compose pull` 卡住 |
国内直连 Docker Hub | 用代理前缀,或配加速器 |
| 客户端显示「未就绪」 | 21116 没通 | 安全组和本机防火墙都查一遍 |
| 能连上但很卡 | P2P 没打通,全走中继 | 检查 21116 UDP 开了没有 |
| 换服务器后客户端连不上 | 密钥变了 | 重新 `cat ./data/id_ed25519.pub` 填进去 |
| 重装服务器后全部客户端失效 | `./data/` 没了,密钥重新生成 |
把 `./data/` 备份出来 |