Ubuntu 部署 RustDesk 服务器

本文面向在 Ubuntu 上自托管 RustDesk 开源服务器(hbbs/hbbr)。适用于本地机房或云服务器(如阿里云、腾讯云、华为云等)。

  • 官方参考文档(强烈建议同时阅读):rustdesk.com/docs/zh-cn/...
  • 适用系统:Ubuntu 20.04/22.04/24.04(其他 Debian 系亦可参考)

0. 准备工作

  • 一台可公网访问的 Ubuntu 服务器(具备 sudo 权限)。
  • 确保可以通过 ssh 登录,执行基础维护命令。
  • 如果使用云厂商,先在云控制台开放端口。

1. 开放端口

云厂商安全组是"第一道防火墙",仅配置系统 ufw 不够,必须在云控制台放行端口。以阿里云为例:

  • 登录 阿里云 ECS 控制台 → 找到你的服务器 → 左侧「安全组」→ 配置规则。
  • 新增「入方向」规则,放行 RustDesk 所需端口:
协议 端口范围 授权对象 说明
TCP 21114-21119 0.0.0.0/0 RustDesk 核心 TCP 端口
UDP 21116 0.0.0.0/0 RustDesk NAT 打洞 UDP 端口

提示:0.0.0.0/0 表示允许所有 IP(适合测试)。生产环境建议缩小到你的办公网段或指定客户端 IP。

保存规则后,通常需等待 1--2 分钟生效(安全组有延迟)。

同时,在服务器系统内使用 ufw 放行端口(建议):

shell 复制代码
sudo apt update
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 21114:21119/tcp
sudo ufw allow 21116/udp
sudo ufw enable
sudo ufw status numbered

2. 安装 RustDesk(方式一:一键脚本)

如果你希望快速完成部署,可使用一键脚本:

shell 复制代码
wget https://ghfast.top/https://raw.githubusercontent.com/techahold/rustdeskinstall/master/install.sh -O install.sh
chmod +x install.sh
sudo ./install.sh

脚本会自动下载安装并以 systemd 服务运行 hbbs(ID 服务)与 hbbr(中继服务)。

  • 安装过程中会出现两处交互确认,按提示选择即可(见下图)。
  • 首次启动时,服务会在日志中输出一个 Key(密钥),请复制并保存(见下图)。

查看服务状态与日志:

shell 复制代码
systemctl status hbbs
systemctl status hbbr
journalctl -u hbbs -f
journalctl -u hbbr -f

检查端口是否监听:

shell 复制代码
ss -tunlp | grep -E '2111[4-9]|21116'

3. 安装 RustDesk(方式二:官方 deb 包,二选一)

也可在 GitHub Releases 下载官方 .deb 包并安装(适合熟悉 Debian 包管理的用户):

  1. 前往 RustDesk 服务器开源版发布页,下载适合架构的 *.deb

  2. 上传到服务器并安装:

    shell 复制代码
    sudo dpkg -i rustdesk-server*.deb
    sudo apt -f install
  3. 验证服务:

    shell 复制代码
    systemctl status hbbs
    systemctl status hbbr

两种安装方式任选其一即可,不必重复安装。

4. 客户端配置(Windows/macOS/Linux)

在 RustDesk 客户端中填写自托管信息(见下图):

  • 打开 RustDesk → 进入「设置」→ 找到与「自托管 / 服务器」相关的配置。
  • 填写以下内容:
    • ID 服务器:你的服务器域名或公网 IP(端口默认 21114)。
    • 中继服务器:你的服务器域名或公网 IP(默认端口 21117,可在客户端处填写为 IP:21117)。
    • Key:从服务器日志中复制的密钥。

保存后,返回主界面,两个客户端互相输入对方 ID 进行连接测试。若能直连,会显示直连;若 NAT 复杂,则会自动走中继。

5. 远程连通性验证(可选但推荐)

从你的本地电脑测试云端端口是否可达:

shell 复制代码
nc -zv <你的服务器IP或域名> 21114
nc -zv <你的服务器IP或域名> 21117

如显示 succeededopen,表示端口连通性正常。

6. 常见问题与排查

  • 无法获取 ID:
    • 检查 hbbs 服务是否运行:systemctl status hbbs
    • 检查云安全组与 ufw 是否已放行 21114--21119/TCP 与 21116/UDP。
  • 连接总是走中继:
    • 很可能是客户端之间 NAT 穿透失败。确保 21116/UDP 放行;或接受通过中继连接。
  • 提示 Key 不匹配:
    • 确保客户端填写的 Key 与服务器当前输出的一致;变更服务器后需更新客户端配置。
  • 服务名不确定:
    • 可执行 systemctl list-units | grep hbbsgrep hbbr 查找具体服务名称。

7. 运维与升级(简要)

  • 重启服务:

    shell 复制代码
    sudo systemctl restart hbbs
    sudo systemctl restart hbbr
  • 日志查看:

    shell 复制代码
    journalctl -u hbbs --no-pager -n 200
    journalctl -u hbbr --no-pager -n 200
  • 安全建议:

    • 生产环境将安全组授权对象从 0.0.0.0/0 收紧到可信网段。
    • 定期更新系统并重启服务,减少已知漏洞风险。

8. 一键更换默认端口脚本(含安全收敛)

为便于快速落地,提供一键脚本:

  • hbbs/hbbr 在自定义端口监听(需要提供中继主机与可选密钥文件)。

端口范围与建议:

  • 可修改范围:1024--65535(避免使用 0--1023 的保留端口)。
  • 推荐选择高位端口(如 20000--40000),并提前检查占用:ss -tunlp | grep <PORT>

将脚本文件 rustdesk-port-switch.sh 上传到服务器(例如 /usr/local/sbin/),并赋予执行权限:

shell 复制代码
#!/usr/bin/env bash
set -euo pipefail

# 常量: 默认端口
DEFAULT_NAT=21116
DEFAULT_RELAY=21117
DEFAULT_HBBS_WS=21118
DEFAULT_HBBR_WS=21119

BASE=""
NAT_PORT=""
RELAY_PORT=""
HBBS_WS=""
HBBR_WS=""
RELAY_HOST=""

# 函数: 打印用法说明与端口范围
usage() {
  cat <<'USAGE'
用法: sudo bash rustdesk-port-switch.sh
说明: 纯交互式,直接修改 hbbs/hbbr 监听端口(override 模式)
端口范围: 1024--65535,建议 20000--40000,避免 0--1023 保留端口
USAGE
}

# 函数: 检查是否 root
require_root() {
  if [ "${EUID:-$(id -u)}" -ne 0 ]; then
    echo "需要以 root 运行" >&2
    exit 1
  fi
}

# 函数: 验证端口范围与合法性
validate_port() {
  local p="$1"
  [[ "$p" =~ ^[0-9]+$ ]] || { echo "端口必须为数字: $p" >&2; exit 1; }
  if [ "$p" -lt 1024 ] || [ "$p" -gt 65535 ]; then
    echo "端口超出允许范围(1024--65535): $p" >&2
    exit 1
  fi
}

# 函数: 计算最终端口组合
compute_ports() {
  if [ -n "$BASE" ]; then
    NAT_PORT="$BASE"
    RELAY_PORT=$((BASE+2))
    # WebSocket 端口仅在显式指定时设置
  fi
  : "${NAT_PORT:=${NAT_PORT:-}}"
  : "${RELAY_PORT:=${RELAY_PORT:-}}"
  [ -n "$NAT_PORT" ] || NAT_PORT="$DEFAULT_NAT"
  [ -n "$RELAY_PORT" ] || RELAY_PORT="$DEFAULT_RELAY"

  validate_port "$NAT_PORT"; validate_port "$RELAY_PORT"
  [ -n "$HBBS_WS" ] && validate_port "$HBBS_WS"
  [ -n "$HBBR_WS" ] && validate_port "$HBBR_WS"
}

# 函数: 交互式询问模式/端口/安全选项
is_port_used() {
  local p="$1"
  ss -H -l -n | awk '{print $5}' | awk -F: '{print $NF}' | grep -qx "$p"
}

suggest_base_port() {
  local tries=0 max=30 b
  while [ $tries -lt $max ]; do
    b=$((20000 + RANDOM % 20001))
    [ $((b+4)) -le 65535 ] || { tries=$((tries+1)); continue; }
    if is_port_used "$b" || is_port_used $((b+2)) || is_port_used $((b+3)) || is_port_used $((b+4)); then
      tries=$((tries+1)); continue
    fi
    BASE_SUG="$b"
    HBBS_WS_SUG=$((b+3))
    HBBR_WS_SUG=$((b+4))
    return 0
  done
  BASE_SUG=30022
  HBBS_WS_SUG=30025
  HBBR_WS_SUG=30026
}

interactive_prompt() {
  suggest_base_port
  echo "推荐基准端口: ${BASE_SUG} (hbbs(NAT)=${BASE_SUG}, hbbr(Relay)=$((BASE_SUG+2)); WS: hbbs=${HBBS_WS_SUG}, hbbr=${HBBR_WS_SUG})"
  echo "可输入基准端口(如 30022), 将自动设置: hbbs(NAT)=base, hbbr(Relay)=base+2"
  read -r -p "基准端口(回车使用推荐 ${BASE_SUG}): " BASE
  if [ -z "$BASE" ]; then BASE="$BASE_SUG"; fi
  validate_port "$BASE"

  NAT_PORT="$BASE"; RELAY_PORT=$((BASE+2))

  # 可选 WebSocket 端口
  read -r -p "是否设置 WebSocket 端口(推荐 hbbs=${HBBS_WS_SUG} hbbr=${HBBR_WS_SUG},官方默认 21118/21119)[y/N]: " ws_sel
  case "${ws_sel,,}" in
    y|yes)
      echo "推荐 WebSocket 端口: hbbs=${HBBS_WS_SUG} hbbr=${HBBR_WS_SUG}"
      read -r -p "hbbs WebSocket 端口(回车使用推荐 ${HBBS_WS_SUG}): " HBBS_WS
      [ -z "$HBBS_WS" ] && HBBS_WS="$HBBS_WS_SUG"
      validate_port "$HBBS_WS"
      read -r -p "hbbr WebSocket 端口(回车使用推荐 ${HBBR_WS_SUG}): " HBBR_WS
      [ -z "$HBBR_WS" ] && HBBR_WS="$HBBR_WS_SUG"
      validate_port "$HBBR_WS"
      ;;
    *) : ;;
  esac

  read -r -p "中继主机(域名或IP): " RELAY_HOST
  if [ -z "$RELAY_HOST" ]; then
    echo "需要提供中继主机"; exit 1
  fi

  echo "\n配置摘要:";
  echo "hbbs(NAT) 端口: ${NAT_PORT}  hbbr(Relay) 端口: ${RELAY_PORT}"
  [ -n "$HBBS_WS" ] && echo "hbbs WS: ${HBBS_WS}"
  [ -n "$HBBR_WS" ] && echo "hbbr WS: ${HBBR_WS}"
  echo "中继主机: ${RELAY_HOST}"

  read -r -p "继续执行? [y/N]: " go
  case "${go,,}" in y|yes) : ;; *) echo "已取消"; exit 1;; esac
}

# (移除 NAT 重定向逻辑)

# 函数: UFW 开放新端口
ensure_ufw_rules() {
  if ! command -v ufw >/dev/null 2>&1; then
    apt-get update -y && apt-get install -y ufw
  fi
  ufw allow "${NAT_PORT}/udp" || true
  ufw allow "${NAT_PORT}/tcp" || true
  ufw allow "${RELAY_PORT}/tcp" || true
  [ -n "$HBBS_WS" ] && ufw allow "${HBBS_WS}/tcp" || true
  [ -n "$HBBR_WS" ] && ufw allow "${HBBR_WS}/tcp" || true
  ufw status | cat
}

# (移除持久化 iptables 逻辑)

# 函数: 写入 systemd override
write_override() {
  local hbbs_path hbbr_path unit path_guess

  # 优先使用用户指定路径
  hbbs_path="${HBBS_PATH:-}"
  hbbr_path="${HBBR_PATH:-}"

  # 通过 PATH 查找
  [ -z "$hbbs_path" ] && hbbs_path="$(command -v hbbs || true)"
  [ -z "$hbbr_path" ] && hbbr_path="$(command -v hbbr || true)"

  # 通过 systemd 单元解析 ExecStart
  if [ -z "$hbbs_path" ]; then
    unit="$(systemctl cat hbbs 2>/dev/null || true)"
    path_guess="$(echo "$unit" | awk -F= '/^ExecStart=/{print $2}' | awk '{print $1}' | tail -n1)"
    [ -n "$path_guess" ] && hbbs_path="$path_guess"
  fi
  if [ -z "$hbbr_path" ]; then
    unit="$(systemctl cat hbbr 2>/dev/null || true)"
    path_guess="$(echo "$unit" | awk -F= '/^ExecStart=/{print $2}' | awk '{print $1}' | tail -n1)"
    [ -n "$path_guess" ] && hbbr_path="$path_guess"
  fi

  # 常见路径回退
  [ -z "$hbbs_path" ] && for p in /usr/local/bin/hbbs /usr/bin/hbbs /opt/rustdesk/hbbs /opt/rustdesk-server/hbbs; do [ -x "$p" ] && hbbs_path="$p" && break; done
  [ -z "$hbbr_path" ] && for p in /usr/local/bin/hbbr /usr/bin/hbbr /opt/rustdesk/hbbr /opt/rustdesk-server/hbbr; do [ -x "$p" ] && hbbr_path="$p" && break; done

  [ -n "$hbbs_path" ] && [ -x "$hbbs_path" ] || { echo "未找到可执行的 hbbs, 请确认安装在 /opt/rustdesk/hbbs 或 /usr/local/bin/hbbs"; exit 1; }
  [ -n "$hbbr_path" ] && [ -x "$hbbr_path" ] || { echo "未找到可执行的 hbbr, 请确认安装在 /opt/rustdesk/hbbr 或 /usr/local/bin/hbbr"; exit 1; }

  # 写入 systemd 配置:若主单元存在则写 override;否则创建新的单元文件
  if systemctl cat hbbs >/dev/null 2>&1; then
    mkdir -p /etc/systemd/system/hbbs.service.d
    cat >/etc/systemd/system/hbbs.service.d/override.conf <<EOF
[Service]
ExecStart=
ExecStart=${hbbs_path} -p ${NAT_PORT} -r ${RELAY_HOST}:${RELAY_PORT}
EOF
  else
    cat >/etc/systemd/system/hbbs.service <<EOF
[Unit]
Description=RustDesk HBBS (ID server)
After=network.target

[Service]
Type=simple
ExecStart=${hbbs_path} -p ${NAT_PORT} -r ${RELAY_HOST}:${RELAY_PORT}
Restart=always
RestartSec=2s
User=root

[Install]
WantedBy=multi-user.target
EOF
  fi

  if systemctl cat hbbr >/dev/null 2>&1; then
    mkdir -p /etc/systemd/system/hbbr.service.d
    cat >/etc/systemd/system/hbbr.service.d/override.conf <<EOF
[Service]
ExecStart=
ExecStart=${hbbr_path} -p ${RELAY_PORT}
EOF
  else
    cat >/etc/systemd/system/hbbr.service <<EOF
[Unit]
Description=RustDesk HBBR (Relay server)
After=network.target

[Service]
Type=simple
ExecStart=${hbbr_path} -p ${RELAY_PORT}
Restart=always
RestartSec=2s
User=root

[Install]
WantedBy=multi-user.target
EOF
  fi
}

# 函数: 重载并重启服务
restart_services() {
  systemctl daemon-reload
  systemctl enable hbbs >/dev/null 2>&1 || true
  systemctl enable hbbr >/dev/null 2>&1 || true
  systemctl restart hbbs || true
  systemctl restart hbbr || true
  systemctl --no-pager -q status hbbs hbbr || true
}

# 函数: 提取并保存最新 Key 到脚本同级目录,并打印
save_and_print_key() {
  local key line script_dir key_file
  # 从 hbbs 日志提取最新 Key
  line=$(journalctl -u hbbs -n 200 --no-pager 2>/dev/null | grep -E "Key:" | tail -n1 || true)
  if [ -z "$line" ]; then
    line=$(journalctl -u hbbr -n 200 --no-pager 2>/dev/null | grep -E "Key:" | tail -n1 || true)
  fi
  key=$(echo "$line" | sed -E 's/^.*Key:\s*([A-Za-z0-9+\/=]+).*$/\1/' )
  script_dir=$(cd -- "$(dirname -- "$0")" && pwd)
  key_file="$script_dir/rustdesk.key"
  if [ -n "$key" ]; then
    printf "%s\n" "$key" > "$key_file"
    chmod 600 "$key_file" 2>/dev/null || true
    echo "- ID 服务器 : ${RELAY_HOST}:${NAT_PORT}"
    echo "- 中继服务器 : ${RELAY_HOST}:${RELAY_PORT}"
    echo "- Key: $key"
    echo "已保存到: $key_file"
  else
    echo "- ID 服务器 : ${RELAY_HOST}:${NAT_PORT}"
    echo "- 中继服务器 : ${RELAY_HOST}:${RELAY_PORT}"
    echo "- Key: (未解析)"
    echo "未从日志解析到 Key。可稍后运行: journalctl -u hbbs -n 200 | grep -i Key"
  fi
}

require_root
interactive_prompt

if [ -z "$RELAY_HOST" ]; then
  echo "需要提供中继主机" >&2
  exit 1
fi
write_override
restart_services
ensure_ufw_rules
save_and_print_key
shell 复制代码
sudo mv rustdesk-port-switch.sh /usr/local/sbin/
sudo chmod +x /usr/local/sbin/rustdesk-port-switch.sh

交互式运行(无需参数):

shell 复制代码
sudo bash /usr/local/sbin/rustdesk-port-switch.sh
  • 将依次询问基准端口或分别输入 hbbs/hbbr 端口、是否设置 WebSocket 端口,以及中继主机与密钥文件,确认后自动执行。
  • 已内置自动探测 hbbs/hbbr 路径(包含 /opt/rustdesk/hbbs/opt/rustdesk/hbbr 等常见位置),无需手动输入路径。
  • 每次运行脚本都会随机推荐一组高位端口(避开已占用),包含 hbbs(NAT)hbbr(Relay) 以及可选的 WebSocket 端口;直接回车可使用推荐值。
  • 脚本会在执行完成后自动从日志解析最新 Key,并保存为脚本同级目录的 rustdesk.key(覆盖旧文件),同时在终端打印当前 Key

执行完成后终端将输出如下摘要,便于在客户端填写:

  • ID 服务器 : <你的域名或IP>:<hbbs端口>
  • 中继服务器 : <你的域名或IP>:<hbbr端口>
  • Key: <服务器当前Key>
  • 已保存到: <脚本同级目录>/rustdesk.key

记得去阿里云等安全组开放重新设置的端口

验证:3001[5-9]记得换成<新设置的端口>

shell 复制代码
ss -tunlp | grep -E '3001[5-9]'
nc -zv <域名或IP> <新设置的端口>
nc -zv <域名或IP> <新设置的端口>

9. 端口说明(了解即可)

RustDesk 服务器默认使用 21114--21119/TCP21116/UDP

  • 21116/UDP:NAT 打洞。
  • 21117/TCP:中继通道。
  • 其余端口用于 ID 注册、握手与控制信令等(保留默认即可)。
相关推荐
徒 花1 天前
ubuntu远程连接ssh及VSCode配置远程ssh连接ubuntu
vscode·ubuntu·ssh
i建模1 天前
在Ubuntu中解压ZIP文件
linux·chrome·ubuntu
林鸿群1 天前
Ubuntu 26.04 本地安装 GitLab CE 完整教程(非 Docker 方式)
linux·ubuntu·gitlab·私有部署·代码托管·ubuntu 26.04·omnibus
YuQiao03031 天前
国内安装claude code
ubuntu·claude·vibe coding
Jiozg1 天前
ES安装到linux(ubuntu)
linux·ubuntu·elasticsearch
returnthem1 天前
Ubuntu 22.04 + XFCE4 + 非 Snap 版 Firefox + VNC/noVNC 部署全步骤
linux·ubuntu·firefox
wq8973871 天前
[AI问答]Ubuntu 24.04 上 PyTorch的环境搭建
人工智能·pytorch·ubuntu
邓草1 天前
Ubuntu修改docker数据目录的方法
ubuntu·docker·eureka
艾莉丝努力练剑1 天前
【Linux:文件 + 进程】进程间通信进阶(2)
linux·运维·服务器·开发语言·网络·c++·ubuntu
齐齐大魔王2 天前
虚拟机网络无法连接
linux·网络·c++·python·ubuntu