This guide is for administrators, security reviewers, packagers and developers working with the RustDesk client 1.5.0 (commit a7f2260). It covers architecture, the complete configuration model, deployment and packaging, the security model, operations, performance tuning, integrations with a self-hosted server API, and developer internals. Day-to-day usage is in the companion User Guide.
1. Executive Summary
| Item | Summary |
|---|---|
| What it is | Cross-platform remote desktop client and host: screen/audio streaming, input injection, file transfer, clipboard, terminal, TCP tunneling, camera view, recording |
| Built with | Rust 2021 (MSRV 1.75) core in a Cargo workspace; Flutter UI (Dart SDK ^3.1.0) bridged with flutter_rust_bridge 1.80; legacy Sciter UI still buildable |
| Codecs | libvpx (VP8/VP9), libaom (AV1), FFmpeg-based hardware H.264/H.265 (VAAPI, NVENC, AMF, QSV, VideoToolbox, MediaCodec), Windows D3D11 "VRAM" path; Opus audio |
| Network model | Client registers with an ID/rendezvous server (hbbs, 21116); sessions race TCP/UDP-KCP/IPv6/WebRTC hole punching and fall back to a relay (hbbr, 21117) |
| Crypto | Ed25519 identities, X25519 box key exchange, XSalsa20-Poly1305 secretbox session encryption, per-direction subkeys (kx v1) |
| Platforms | Windows (x64, arm64, x86 Sciter), macOS 10.14+, Linux (deb/rpm/Arch/AppImage/Flatpak), Android 5.1+, iOS 13+ (controller only) |
| Deployment shape | Desktop: privileged OS service + per-user server + tray + connection manager processes. No server component in this repo — the ID/relay servers are separate projects. |
| Licence | GNU AGPL-3.0 (the RPM specs' GPL-3.0 field is inconsistent with LICENCE) |
| Operational burden | Low for clients (auto-update on installed Windows, update checks elsewhere). Real work is in server choice, key distribution, policy (permissions, approve mode, 2FA) and Linux display-server constraints. |
Important
The default configuration uses the public ID server
rs-ny.rustdesk.comwith keyOeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=. Organizations that need to control metadata and relay paths should deploy their own server and distributecustom-rendezvous-server,relay-server,api-serverandkeyto every client.
2. Architecture
2.1 System overview
+----------------------------+
| ID / rendezvous (hbbs) | TCP+UDP 21116, TCP 21115 (NAT test)
| optional HTTP API 21114 | WebSocket 21118
+-------------+--------------+
RegisterPeer/Pk (15 s) | PunchHole / FetchLocalAddr / RequestRelay
+-------------------------------+-------------------------------+
| |
+-------v---------------------------+ +---------------v-------------------+
| CONTROLLER (client) | | CONTROLLED (host) |
| Flutter UI <-FFI-> librustdesk | race: TCP punch | --service (SYSTEM/root) |
| session / io_loop / decoders |<=== UDP+KCP ====> | supervises --server |
| port_forward, file jobs | IPv6+KCP | --server (user): RendezvousMediator|
| | WebRTC | Connection, services: |
+-------------------+---------------+ | monitorN, audio, clipboard, |
| fallback | input, display, printer, |
| +----------------------+ | terminal (per connection) |
+==>| relay (hbbr) 21117 |<=======+ --cm Connection Manager (UI) |
| WS relay 21119 | | --tray tray icon |
+----------------------+ +------------------------------------+
2.2 Component inventory
| Component | Path | Role |
|---|---|---|
| Entry & CLI dispatch | src/main.rs, src/core_main.rs |
Argument parsing, process roles, install/uninstall commands |
| Rendezvous client | src/rendezvous_mediator.rs |
Registration, NAT test, punch/relay handling on the host, direct-IP listener |
| Client connection | src/client.rs, src/client/io_loop.rs |
Punch/relay race, secure handshake, login, decode, session I/O |
| Server core | src/server.rs, src/server/* |
Service registry, per-connection handling (connection.rs), video/audio/input/clipboard/display/printer/terminal services |
| Media capture & codecs | libs/scrap |
DXGI/GDI/Magnifier, Quartz, X11 SHM, Wayland PipeWire, DRM/KMS, Android; VPX/AOM/hwcodec/VRAM encoders; recording |
| Input injection | libs/enigo, src/keyboard.rs, src/server/uinput.rs, src/server/rdp_input.rs |
Keyboard modes, uinput devices, portal input |
| Clipboard | src/clipboard.rs, src/clipboard_file.rs, libs/clipboard |
Text/image/HTML sync, file copy-paste (CLIPRDR, FUSE, macOS placeholder) |
| Shared core | libs/hbb_common (git submodule) |
Config, protobuf protocol, sockets, TLS, proxy, WebSocket, WebRTC, password security |
| Client-only base | libs/base |
Option keys (config/keys.rs), message protocol, file-transfer engine (fs.rs), platform helpers |
| HTTP API client | src/hbbs_http/* |
Heartbeat/sysinfo sync, OIDC account login, downloader, record upload, HTTP client |
| IPC | src/ipc.rs, src/ipc/* |
Local IPC between service, server, UI, CM, tray |
| Platform layer | src/platform/* |
Windows/macOS/Linux service install, privileges, session handling |
| Privacy mode | src/privacy_mode* |
Windows exclude-from-capture / magnifier / virtual display, macOS gamma + event tap |
| Virtual display | libs/virtual_display, src/virtual_display_manager.rs |
Amyuni IDD (selected) / RustDesk IDD on Windows 10 19041+ |
| Remote printer | libs/remote_printer, src/server/printer_service.rs |
Windows virtual printer + job forwarding |
| Whiteboard | src/whiteboard |
--whiteboard overlay process for "show my cursor" |
| Flutter UI | flutter/lib |
Desktop and mobile UI (desktop/, mobile/, common/, models/) |
| FFI bridge | src/flutter_ffi.rs, src/flutter.rs |
~315 functions exposed to Dart |
| Legacy UI | src/ui/*, src/ui.rs |
Sciter HTML/TIS UI (deprecated) |
| Portable packer | libs/portable |
Windows self-extracting executable |
| Packaging | res/, appimage/, flatpak/, res/msi |
deb/rpm/Arch/AppImage/Flatpak/MSI/systemd/launchd |
2.3 Cargo workspace layers
| Layer | Crates | Notes |
|---|---|---|
| Application | rustdesk (lib librustdesk as cdylib/staticlib/rlib; bins rustdesk, naming, service) |
default-run = "rustdesk"; service is the macOS launchd daemon binary |
| Shared protocol & config | libs/hbb_common |
Submodule shared with the server; changes cost a server round-trip, so client-only code goes to libs/base |
| Client platform | libs/base, libs/enigo, libs/clipboard, libs/scrap |
|
| Windows extras | libs/virtual_display, libs/virtual_display/dylib, libs/remote_printer, libs/portable |
|
| Stubs & patches | libs/libxdo-sys-stub (patched in for libxdo-sys), forked tungstenite, webrtc, webrtc-util, webrtc-sctp |
libxdo is loaded at runtime; missing libxdo makes X11 input a no-op |
2.4 Process model
| Process | Started by | Runs as | Purpose |
|---|---|---|---|
rustdesk --service (macOS: .../MacOS/service) |
OS service manager | SYSTEM / root | Supervisor; owns _service IPC, launches --server in the active session, handles SAS, uinput, DRM export |
rustdesk --server |
service, launchd agent, or main UI fallback | user (root at Linux login screen) | Owns main IPC, rendezvous registration, accepts sessions |
rustdesk --tray |
UI / server / Startup shortcut | user | Tray menu Open, Stop service; tooltip with session count |
rustdesk --cm |
server on first incoming connection | user | Connection Manager window |
| main UI | user | user | Outgoing sessions and settings; runs server logic in-process when no --server is reachable |
| portable service | Quick-support / not-installed Windows | elevated / SYSTEM | Capture + input across UAC via shared memory |
--terminal-helper |
server (Windows) | user | ConPTY host for remote terminals |
--whiteboard |
server | user | Cursor/annotation overlay |
IPC endpoints: Windows named pipe \\.\pipe\RustDesk\query<postfix>; Linux/macOS /tmp/RustDesk-<euid>/ipc<postfix> (the _service and _uinput_* postfixes live in /tmp/RustDesk-service/). Postfixes: "" (main), _service, _cm, _pa (PulseAudio on Linux), _url (macOS), _whiteboard, _portable_service, _uinput_*, _drm.
Note
If a
--servercannot bind the main IPC, it logsipc is occupied by another process, try kill it, kills the main-window process and exits. Non-server processes pull configuration from the server over IPC at start (Data::SyncConfig), so the--serverprocess is the source of truth for options at runtime.
2.5 Connection establishment
- Controller guards: incoming-only builds refuse (
Incoming only mode). - Direct IP or
domain:porttargets skip rendezvous and connect by TCP (bare IP → port 21118). - In parallel the controller prepares an IPv6 probe, a UDP NAT socket, and a WebRTC offer (disabled with a proxy).
- The rendezvous socket is secured (
secure_tcp) when a WebRTC offer rides on it (mandatory) or when a key plus token/switch code exists. PunchHoleRequest{id, token, nat_type, licence_key, conn_type, version, udp_port, force_relay, socket_addr_v6, switch_code, webrtc_sdp_offer}goes tohbbs.- The host reacts to
PunchHole(UDP/TCP punch, IPv6, WebRTC answer),FetchLocalAddr(same LAN) orRequestRelay. - The controller races TCP, UDP/KCP, IPv6/KCP and WebRTC; direct timeouts are 1 s on LAN or symmetric NAT, else punch time ×6 (×3 after a previous direct failure).
- On failure or forced relay:
RequestRelay→ TCP tohbbr21117 with the shared UUID. - Secure handshake (section 6), then login.
| Constant | Value |
|---|---|
RENDEZVOUS_TIMEOUT |
12 000 ms |
CONNECT_TIMEOUT / READ_TIMEOUT |
18 000 ms |
REG_INTERVAL |
15 000 ms |
| Punch attempts | 3 (deadlines 3 s, 6 s, 9 s) |
| Relay attempts | 3 |
RELAY_FALLBACK_DELAY_MS |
2 500 (option relay-fallback-delay) |
| Host punch caps | 32 UDP + 32 TCP in flight, 16 WebRTC answerers |
| Unauthenticated connections | 64 total, 16 per IP, 128 KiB max message, 180 s login grace |
Labels shown to users: TCP, UDP, IPv6, WebRTC, WebRTC/IPv6, Relay, WebSocket. direct = false for relay, WebSocket and TURN-relayed WebRTC.
2.6 Service model on the host
Services are GenericService threads keyed by name: audio, display, clipboard, file-clipboard (feature), mouse_cursor, mouse_pos, window_focus (desktop; X11 only on Linux), remote-printer (Windows Flutter), and lazily created monitor<N> / camera<N> video services. Terminal services are per connection.
- A connection subscribes only to services its permissions allow (for example no
audioif audio is disabled, noclipboardwithout clipboard permission). - The
run()loop backs off from 30 ms to 1000 ms on errors and logsError of {name} service: {err}. - Any change needing a new capturer/encoder raises
SWITCH, which restarts the video service (codec change, new subscriber, quality change on encoders that cannot change on the fly, privacy-mode change, recording change).
2.7 Caching
| Cache | Location / scope | Behaviour |
|---|---|---|
| Hardware codec capability | config file RustDesk_hwcodec (Windows/macOS) |
Written by the --check-hwcodec-config child; reused while the GPU signature is unchanged |
| AV1 speed test | option av1-test (Y/N) |
10 synthetic 1080p frames at startup; key frame < 90 ms and deltas < 30 ms to pass |
| TLS mode per host:port | in memory | rustls vs native-tls and invalid-cert acceptance; cleared when proxy/TLS options change |
| Address book / group | RustDesk_ab, RustDesk_group |
Compressed, encrypted JSON; max 64 MiB; deleted if unreadable |
| Peer list | peers/*.toml |
Sorted newest first, loaded in batches of 100; preloaded on a background thread |
| Cursor shapes | in memory | 4096 handles / 32 MB |
| Wayland consent | LocalConfig wayland-restore-token |
ScreenCast portal v4+ with persist_mode = 2 |
| Login failures | in memory (LOGIN_FAILURES) |
Lost on restart |
Note
Values equal to a default (including a custom-client default) are never written to disk, and options present in an override layer are removed from disk. Config files therefore do not show effective defaults; read effective values with
rustdesk --option <key>.
3. System Requirements
3.1 Runtime platforms
| Platform | Minimum / notes |
|---|---|
| Windows | 64-bit x64 or arm64 Flutter builds; 32-bit x86 Sciter build. System audio loopback needs Windows 10 1703+. Virtual displays need Windows 10 19041 (2004)+. Remote printer and "show my cursor" need Windows 10+. HDR output is converted to SDR (SDR white level needs 1709+). |
| macOS | 10.14 deployment target. Needs Screen Recording, Accessibility and Input Monitoring permissions; launchd daemon + agent for unattended access. ScreenCaptureKit is used only for audio. |
| Linux X11 | XCB MIT-SHM capture; libxdo loaded at runtime (missing = no X11 input); PulseAudio monitor source for audio |
| Linux Wayland | xdg-desktop-portal + PipeWire + GStreamer; Ubuntu 21.04+; consent dialog unless ScreenCast restore token; GDM Wayland greeter only with the drm build (rustdesk-unattended-wayland) |
| Android | minSdk 22, target/compile SDK 36; audio capture from SDK 30 (Android 11); accessibility service for input |
| iOS | 13.0+; controller only |
3.2 Linux runtime dependencies (Debian control)
libgtk-3-0t64 | libgtk-3-0, libxcb-randr0, libxdo3 | libxdo4, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2t64 | libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, gstreamer1.0-pipewire (+ libatomic1 on armhf); Recommends libayatana-appindicator3-1. The DRM variant adds libdrm2 (>= 2.4.101), libegl1, libgles2 and a measured glibc floor.
3.3 Build toolchain
| Tool | Version (CI pins) |
|---|---|
| Rust | 1.75 (Sciter/i128 ABI constraint); macOS 1.81 (cidre) |
| Flutter | 3.24.5 (Windows arm64: 3.44.9; Linux arm64: flutter-elinux 3.16.9) |
| flutter_rust_bridge | 1.80.1 (codegen needs cargo-expand 1.0.95) |
| vcpkg | commit 9e593bb18ea69cc5095e012465dcd675a822ed0d (baseline in vcpkg.json) |
| Android NDK | r28c; cargo-ndk 3.1.2 |
| LLVM | 15.0.6 |
| vcpkg overlay ports | aom 3.14.1, ffmpeg 7.1.1 (13 patches), libvpx 1.15.2, libyuv 1857, mfx-dispatch 1.35.1, opus 1.5.2 |
3.4 Hardware guidance
| Resource | Guidance from the code |
|---|---|
| RAM | On hosts with ≤ 4 GB total RAM, auto codec selection prefers VP8 over VP9/AV1 |
| CPU | Encoder threads = idle-CPU based, ≤ CPUs/2 and ≤ free GB/2, rounded to a power of two (VPX/AOM max 64, FFmpeg max 16). AV1 disabled on 32-bit builds and on hosts failing the self-test. |
| GPU | Hardware H.264/H.265 via FFmpeg hwcodec; Windows VRAM path needs DXGI capture + enable-hwcodec (+ allow-d3d-render on the viewer) |
| Network | VP8/VP9 target at 1920×1080 = 2073 kbps base × quality ratio: ≈ 1.4 Mbps Balanced (0.67), ≈ 1.0 Mbps Optimize reaction time (0.5), ≈ 3.1 Mbps Good image quality (1.5); adaptive bitrate moves the ratio between 0.1 and 40 |
4. Configuration Mastery
4.1 Files and locations
All files are {APP_NAME}{suffix}.toml (TOML via confy, mode 0600 on Unix). APP_NAME defaults to RustDesk and changes with a custom client's app-name.
| Struct | File | Contents |
|---|---|---|
Config |
RustDesk.toml |
enc_id (encrypted ID), password (hashed), salt, key_pair, key_confirmed, keys_confirmed |
Config2 |
RustDesk2.toml |
rendezvous_server, nat_type, serial, unlock_pin (encrypted), trusted_devices (encrypted), socks (proxy), options (all server/"Settings" options) |
LocalConfig |
RustDesk_local.toml |
remote_id, kb_layout_type, window size, fav, options (local UI options), ui_flutter |
UserDefaultConfig |
RustDesk_default.toml |
Display defaults for new peers (re-read at most every 1 s) |
Status |
RustDesk_status.toml |
Free key/value status |
LanPeers |
RustDesk_lan_peers.toml |
Discovered LAN peers |
PeerConfig |
peers/<id>.toml |
Per-peer session settings; IDs with `< > : / \ |
| Address book / group cache | RustDesk_ab, RustDesk_group |
Encrypted compressed JSON |
| OS | Config directory | Log directory |
|---|---|---|
| Windows user | %APPDATA%\RustDesk\config |
%APPDATA%\RustDesk\log |
| Windows service | C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config (any system32\config\systemprofile path is rewritten) |
...\RustDesk\log |
| Linux | ~/.config/rustdesk (a root process whose whoami is not root uses that user's home) |
~/.local/share/logs/RustDesk |
| macOS | ~/Library/Preferences/com.carriez.RustDesk (Application Support rewritten to Preferences) |
~/Library/Logs/RustDesk |
| Android | app dir passed from Java | <home>/RustDesk/Logs |
Note
The Windows and Linux folder names come from the
directories_nextcrate's conventions combined with RustDesk's path rewrites. There is no environment variable that relocates the config directory.
4.2 Resolution layers
| Layer | Storage | Lookup order |
|---|---|---|
| Server / Settings options | RustDesk2.toml [options] |
OVERWRITE_SETTINGS → file → DEFAULT_SETTINGS |
| Local options | RustDesk_local.toml [options] |
OVERWRITE_LOCAL_SETTINGS → file → DEFAULT_LOCAL_SETTINGS |
| Display defaults | RustDesk_default.toml [options] |
OVERWRITE_DISPLAY_SETTINGS → file → DEFAULT_DISPLAY_SETTINGS → per-key validation |
| Built-in (custom client) | memory BUILTIN_SETTINGS |
Custom client only; default "" |
| Hard settings (custom client) | memory HARD_SETTINGS |
Custom client only |
Boolean convention (option2bool):
| Key pattern | True when | Default when empty |
|---|---|---|
enable-* |
value ≠ N |
on |
allow-*, stop-service, direct-server, force-always-relay |
value = Y |
off |
| anything else | value ≠ N |
on |
4.3 Server / Settings options (RustDesk2.toml)
| Key | Values | Default | Effect |
|---|---|---|---|
access-mode |
full, view, other = custom |
custom | Flutter desktop: full forces all permissions on, view all off |
enable-keyboard |
Y/N | on | Keyboard/mouse control |
enable-clipboard |
Y/N | on | Clipboard (also needs keyboard permission) |
enable-file-transfer |
Y/N | on | File transfer sessions and file clipboard |
enable-camera |
Y/N | on | Camera view sessions |
enable-terminal |
Y/N | on | Terminal sessions |
enable-remote-printer |
Y/N | on | Print jobs to the controller |
enable-audio |
Y/N | on | Audio stream |
enable-tunnel |
Y/N | on | TCP tunnels / RDP |
enable-remote-restart |
Y/N | on | Restart remote device |
enable-record-session |
Y/N | on | Controller may record |
enable-block-input |
Y/N | on | Block user input (Windows) |
enable-privacy-mode |
Y/N | on | Privacy mode |
enable-lan-discovery |
Y/N | on | Answer LAN discovery pings |
enable-trusted-devices |
Y/N | on | Trusted devices skip 2FA |
enable-abr |
Y/N | on | Adaptive bitrate |
enable-hwcodec |
Y/N | on | Hardware codecs + probe process |
enable-directx-capture |
Y/N | on | Windows DXGI capture (needed for VRAM encode) |
enable-android-software-encoding-half-scale |
Y/N | on | Android half-scale software encode |
keep-awake-during-incoming-sessions |
Y/N | on | Keep host awake |
approve-mode |
password, click, other = both |
both | How sessions are approved |
verification-method |
use-temporary-password, use-permanent-password, other = both |
both | Accepted passwords |
temporary-password-length |
6, 8, 10 |
6 (any other value) | One-time password length |
allow-numeric-one-time-password |
Y | off | Digits-only one-time password |
allow-hide-cm |
Y | off | Hide CM (only with approve=password + permanent-only) |
allow-remote-config-modification |
Y | off | Unblocks the host's own settings UI during remote input (UI-only enforcement) |
allow-auto-disconnect / auto-disconnect-timeout |
Y / minutes | off / 10 | Idle disconnect |
allow-only-conn-window-open |
Y | off | Refuse when main window not running |
allow-auto-record-incoming |
Y | off | Record incoming sessions |
windows-service-video-save-directory |
absolute path | %SystemDrive%\ProgramData\<app>\recording |
Recording dir for Windows service |
allow-remove-wallpaper |
Y | off | Remove wallpaper during sessions |
allow-always-software-render |
Y | off | Linux: sets LIBGL_ALWAYS_SOFTWARE=1 |
allow-auto-update |
Y | off | Automatic update (installed Windows / macOS) |
allow-scope-violation-close / -alarm |
Y | off | Close / audit on out-of-scope messages |
allow-websocket |
Y | off | WebSocket transport (relay only) |
allow-insecure-tls-fallback |
Y | off | Accept invalid TLS certs after failure |
allow-kcp-congestion-control / allow-webrtc-congestion-control |
Y | off | Transport congestion control |
disable-udp |
Y | off | TCP-only rendezvous |
direct-server / direct-access-port |
Y / port | off / 21118 | Direct IP listener |
whitelist |
comma list of IP/CIDR; 0.0.0.0 = all |
empty = all | IP allow-list |
id-whitelist |
comma list, */? wildcards |
empty = all | ID allow-list |
custom-rendezvous-server |
host[:port] |
public | ID server |
relay-server |
host[:port] |
server-supplied / ID host +1 | Relay |
api-server |
URL | derived | HTTP API |
key |
base64 Ed25519 public key | RS_PUB_KEY |
Server key |
ice-servers |
comma list stun:/turn: |
4 default STUN | WebRTC ICE servers |
preset-address-book-*, preset-device-username, preset-device-name, preset-note |
strings | empty | Sent to the API server with sysinfo |
stop-service |
Y | off | Service stopped (no registration, no direct server, no heartbeat) |
2fa, bot |
encrypted JSON / token | empty | TOTP secret and Telegram bot token |
4.4 Local options (RustDesk_local.toml)
| Key | Default | Effect |
|---|---|---|
theme, lang |
unset | UI theme and language (pt/br read as pt-br) |
enable-tcp-punch |
on | TCP hole punching |
enable-udp-punch, enable-ipv6-punch, enable-webrtc |
on for *.rustdesk.com; N when unset on self-hosted servers |
P2P transports |
relay-fallback-delay |
2.5 s | WebRTC preference window |
enable-port-forward-mux |
on | Multiplex tunnels |
allow-d3d-render |
off | Windows VRAM decode |
use-texture-render |
platform-dependent | Flutter texture rendering |
video-save-directory |
platform default | Recording directory |
allow-auto-record-outgoing |
off | Record outgoing sessions |
enable-check-update |
on | Update check at startup |
keep-awake-during-outgoing-sessions |
on | Keep controller awake |
allow-sync-clipboard-between-sessions |
off | Push clipboard to all sessions |
allow-remote-cm-modification |
off | Remote CM changes |
pre-elevate-service |
— | Windows quick-support pre-elevation |
enable-confirm-closing-tabs, enable-open-new-connections-in-tabs |
on | Tab behaviour |
printer-incomming-job-action (sic), allow-printer-auto-print, printer-selected-name |
— | Incoming print jobs |
hide-recording-button |
— | Hide the recording button |
wayland-restore-token |
— | Remembered Wayland screen selection |
4.5 Display defaults (RustDesk_default.toml)
| Key | Default | Allowed |
|---|---|---|
view_style |
original (desktop), adaptive (mobile) |
original, adaptive |
scroll_style |
scrollauto |
scrollauto, scrollbar, scrolledge |
edge-scroll-edge-thickness |
100 | 20–150 |
image_quality |
balanced |
best, balanced, low, custom |
custom_image_quality |
50 | 10–4095 |
custom-fps |
30 | 5–120 |
codec-preference |
auto |
vp8, vp9, av1, h264, h265 |
trackpad-speed |
100 | 10–1000 |
enable-file-copy-paste |
Y |
"", N |
| Booleans | per user default | view_only, show_monitors_toolbar, collapse_toolbar, show_remote_cursor, follow_remote_cursor, follow_remote_window, zoom-cursor, show_quality_monitor, disable_audio, disable_clipboard, lock_after_session_end, privacy_mode, touch-mode, i444, reverse_mouse_wheel, swap-left-right-mouse, displays_as_individual_windows, use_all_my_displays_for_the_remote_session, terminal-persistent, sync-init-clipboard |
Per-peer only: force-always-relay, rdp_port (3389), rdp_username, rdp_password, os-username, os-password (the last four encrypted).
4.6 Custom-client settings
A signed custom.txt (next to the executable; macOS ../Resources/custom.txt) is base64 of an Ed25519-signed JSON verified with the embedded key 5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=. app-name renames the app (service name, IPC paths, URI scheme, file names); default-settings and override-settings fill the DEFAULT/OVERWRITE maps (keys normalized _→-, routed to display, local, server or built-in layers); any other top-level key becomes a hard setting.
Built-in settings (custom client only):
| Key | Effect |
|---|---|
hide-general-settings, hide-security-settings, hide-network-settings, hide-server-settings, hide-proxy-settings, hide-remote-printer-settings, hide-websocket-settings |
Hide settings pages/sections |
hide-tray, hide-stop-service |
No tray icon (except macOS) / no Stop service |
hide-help-cards, hide-username-on-card, hide-powered-by-me, remove-preset-password-warning, main-window-always-on-top, hide-elevate-button-in-accept-window |
UI |
display-name, avatar |
Identity shown to peers |
default-connect-password |
Password used when none saved |
one-way-clipboard-redirection, one-way-file-transfer |
Directional restrictions |
allow-logon-screen-password |
Permanent password at logon/lock screen even in click mode |
allow-deep-link-password, allow-deep-link-server-settings |
Mobile rustdesk://password / config links |
allow-https-21114 |
Keep :21114 on an https API URL |
allow-hostname-as-id |
Hostname becomes the ID |
register-device |
N = no registration, no API, no --assign/--deploy |
file-transfer-max-files |
N>0 limit; 0 = 10 000; unset = unlimited |
disable-change-permanent-password, disable-change-id, disable-unlock-pin |
Block those changes (UI and CLI) |
use-raw-tcp-for-api |
Tunnel API calls over hbbs TCP |
enable-perm-change-in-accept-window |
Allow permission toggles in the accept window |
allow-command-line-settings-when-settings-disabled |
Allow --password/--set-id/--option when settings are disabled |
preset-device-group-name, preset-user-name, preset-strategy-name |
Sent to the API server |
Hard settings: conn-type (incoming/outgoing), disable-tcp-listen, disable-settings, disable-ab, disable-account, disable-installation, preset password + salt.
Important
Built-in and hard settings cannot be set through the UI,
--option, or a config file — only through a correctly signedcustom.txt. A hand-edited file fails signature verification and is ignored (logged only).
4.7 Servers and ports
| Constant | Value |
|---|---|
RENDEZVOUS_SERVERS |
["rs-ny.rustdesk.com"] |
RS_PUB_KEY |
OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw= |
RENDEZVOUS_PORT / RELAY_PORT |
21116 / 21117 |
WS_RENDEZVOUS_PORT / WS_RELAY_PORT |
21118 / 21119 |
| Default API | https://admin.rustdesk.com |
| Default STUN | stun.cloudflare.com:3478, stun.l.google.com:19302, stun.antisip.com:3478, stun.nextcloud.com:443 |
Rendezvous server selection: Windows exe-name host → custom-rendezvous-server → compile-time PROD_RENDEZVOUS_SERVER (empty) → remembered lowest-latency server → built-in list; :21116 appended if no port.
| Derived service | Rule |
|---|---|
| NAT test | ID host:port-1 (21115) |
| Relay | relay-server, else server-supplied, else ID host:port+1 |
| Direct IP listener | direct-access-port, else 21118 |
| API | api-server → exe-name api → http://<id host>:<port-2> (21114) → https://admin.rustdesk.com; https://…:21114 loses :21114 unless allow-https-21114 |
WebSocket (when allow-websocket) |
IPs: ws://ip:21118 / :21119; domains: ws(s)://domain/ws/id and /ws/relay; wss only when api-server starts with https |
4.8 Environment variables
| Variable | Scope | Effect |
|---|---|---|
RUSTDESK_FORCED_DISPLAY_SERVER |
Linux | Override display-server detection (x11/wayland); forwarded to the user server |
XDG_SESSION_ID, XDG_SESSION_TYPE, XDG_CURRENT_DESKTOP, DESKTOP_SESSION |
Linux | Session and desktop detection fallbacks |
DISPLAY, XAUTHORITY |
Linux | Set by the service before starting the X11 server |
WAYLAND_DISPLAY, WAYLAND_SOCKET |
Linux | Wayland endpoint |
FLATPAK_ID |
Linux | Flatpak mode (flatpak-spawn for loginctl) |
APPDIR |
AppImage | Relaunch via $APPDIR/AppRun |
SHELL, LANG, LC_ALL, LC_CTYPE, TERM, TMUX, STY |
Unix terminal | Shell and locale |
COMSPEC |
Windows terminal | Shell fallback |
NO_DIALOG, PRINT_OUT, WRITE_TO_FILE |
Windows | Suppress/print/write message boxes |
SET_FOREGROUND_WINDOW |
Windows | 1 forces foreground |
RUSTDESK_APPNAME |
Windows portable | Exe name used for licence parsing |
IS_TERMINAL_ADMIN |
client | Request an elevated terminal |
rdp_username, rdp_password |
client (Windows) | RDP credentials passed to cmdkey |
LIBGL_ALWAYS_SOFTWARE |
Linux Flutter | Set from allow-always-software-render |
DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION |
Flutter | Skip window position restore |
PULSE_LATENCY_MSEC=60, PIPEWIRE_LATENCY=1024/48000 |
systemd unit | Audio latency |
No environment variable overrides the ID server, relay, API or key.
4.9 Import and export
| Mechanism | Format | Notes |
|---|---|---|
| UI export/import | JSON {host, relay, api, key} → base64url → reversed |
Clipboard; plain JSON also accepted on import |
| QR code (mobile) | config=<export string> |
Mobile scan only; the app never generates a config QR code |
| Deep link | rustdesk://config/<string> |
Mobile, needs built-in allow-deep-link-server-settings |
--config <string> |
host=...,key=...,api=...,relay=... or encoded form |
Installed + admin; silent |
--option <key> [value] |
raw option | Installed + admin; blocked by disable-settings |
--import-config <path> |
RustDesk.toml + sibling RustDesk2.toml |
Copies only if newer; used by the installer |
| Windows exe name | rustdesk-host=H,key=K,api=A,relay=R,.exe or rustdesk-licensed-<reversed b64>.exe |
Windows only; relay persisted only via --config |
5. Deployment
5.1 Deployment options
| Channel | Artifact | Service installed | Best for |
|---|---|---|---|
| Windows EXE (self-extracting) | rustdesk-<ver>-<arch>.exe |
On Install / --silent-install |
Ad-hoc and scripted installs |
| Windows MSI | rustdesk-<ver>-<arch>.msi |
Yes (custom action) | GPO / Intune / SCCM |
| Windows MSI template | rustdesk-template-<arch>.msi |
Yes | Per-customer branded MSI (second cabinet with custom.txt, icons) |
| Debian/Ubuntu | rustdesk-<ver>-<arch>.deb |
systemd (/usr/lib/systemd/system) |
Linux fleets |
| Debian unattended Wayland | rustdesk-unattended-wayland-<ver>.deb |
systemd | Wayland login screens (x86_64) |
| RPM | rustdesk-<ver>-*.rpm, *-suse.rpm |
systemd (/etc/systemd/system) |
Fedora/RHEL/openSUSE |
| Arch | rustdesk-<ver>-*.pkg.tar.zst |
systemd (/etc/systemd/system) |
Arch/Manjaro |
| AppImage / Flatpak | *.AppImage, *.flatpak, Flathub com.rustdesk.RustDesk |
No | Per-user, outgoing-first |
| macOS | rustdesk-<ver>-<arch>.dmg |
launchd daemon + agent via in-app Install | Mac fleets |
| Android | APKs per ABI, universal APK, F-Droid | Android foreground service | Phones/tablets |
5.2 Windows EXE installation layout
| Element | Value |
|---|---|
| Install directory | %ProgramFiles%\RustDesk (32-bit build prefers Program Files (x86)) |
| Service | sc create RustDesk binpath= "\"<exe>\" --service" start= auto DisplayName= "RustDesk Service" (skipped if outgoing-only or stop-service=Y) |
| Uninstall key | HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\RustDesk with UninstallString "<exe>" --uninstall, WindowsInstaller 0 |
| URI / file association | HKCR\rustdesk URL protocol → "<exe>" "%1"; HKCR\.rustdesk\shell\open\command → "<exe>" --play "%1" |
| Firewall | netsh advfirewall firewall add rule name="RustDesk Service" inbound and outbound allow for the exe |
| SAS policy | HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System SoftwareSASGeneration=1 |
| Shortcuts | %PUBLIC%\Desktop\RustDesk.lnk (desktopicon), Start menu RustDesk.lnk + Uninstall RustDesk.lnk (startmenu), Startup RustDesk Tray.lnk (--tray) |
| Install choices | Stored under HKCR\.rustdesk |
| Config at install | A temporary service runs --import-config "<config path>" then is deleted |
| Exe-name licence | key, custom-rendezvous-server, api-server persisted when both key and host exist |
rustdesk-1.5.0-x86_64.exe --silent-install printer=1
(Toast "Installation Successful!"; with the debug argument the batch window stays open for 300 s.)
5.3 Windows MSI
Public properties for msiexec:
| Property | Default | Effect |
|---|---|---|
INSTALLFOLDER |
registry value or ProgramFiles6432Folder\<Product> |
Install directory (normalized to end in \<Product>\) |
CREATESTARTMENUSHORTCUTS |
registry / 1 | 1/Y/y keeps Start-menu shortcuts |
CREATEDESKTOPSHORTCUTS |
registry / 1 | Desktop shortcut |
INSTALLPRINTER |
registry / empty | 1/Y/y installs the RustDesk printer (Windows 8.1+ VersionNT >= 603) |
LAUNCH_TRAY_APP |
Y |
Launch --tray after install |
STOP_SERVICE |
read from the user's RustDesk2.toml on fresh install; service state on upgrade |
Y skips service creation |
CC_CONNECTION_TYPE |
set by preprocess.py --conn-type |
outgoing skips service, tray shortcut and SAS |
Custom actions: CreateStartService, AddFirewallRules / RemoveFirewallRules, AddRegSoftwareSASGeneration, InstallPrinter / UninstallPrinter, TerminateProcesses, TerminateBrokers, TryStopDeleteService, RemoveRuntimeGeneratedFiles, RemoveAmyuniIdd, TryDeleteStartupShortcut, LaunchApp (non-silent only). UpgradeCode = uuid5(NAMESPACE_OID, "<app>.exe"), so upgrades are stable per app name; MajorUpgrade allows same-version upgrades.
Build the MSI: in res/msi, python preprocess.py [-d ../../rustdesk] [--app-name X] [-v X.Y.Z] [--conn-type incoming|outgoing] [--template] [--custom-arp JSON] [-m Manufacturer], then build msi.sln with Visual Studio 2022.
Warning
MSI and EXE installs are mutually exclusive: the MSI blocks installation over an EXE install (
BlockSelfInstalledApp), andinstall_merefuses to run over an MSI install. Pick one installer type per fleet.
5.4 Linux packages
| Package | Service file | Post-install |
|---|---|---|
| deb | /usr/lib/systemd/system/rustdesk.service |
Symlink /usr/bin/rustdesk → /usr/share/rustdesk/rustdesk; daemon-reload, enable, start (systemd only); pkill rewritten to /usr/bin/pkill |
| rpm (Flutter) | /etc/systemd/system/rustdesk.service |
Desktop files, symlink, enable/start, update-desktop-database |
| Arch | /etc/systemd/system/rustdesk.service |
Same via pacman_install hooks |
Unit file (res/rustdesk.service):
[Unit]
Description=RustDesk
Requires=network.target
After=systemd-user-sessions.service
[Service]
Type=simple
ExecStart=/usr/bin/rustdesk --service
ExecStop=pkill -f "rustdesk --"
PIDFile=/run/rustdesk.pid
KillMode=mixed
TimeoutStopSec=30
User=root
LimitNOFILE=100000
Environment="PULSE_LATENCY_MSEC=60" "PIPEWIRE_LATENCY=1024/48000"
[Install]
WantedBy=multi-user.target
The service loop runs --server as root at the login screen (or a Wayland greeter with the DRM build) and as the logged-in user otherwise, with DISPLAY, XAUTHORITY and TERM set. RustDesk actively removes ~/.config/autostart/rustdesk.desktop. A binary is considered "installed" when it lives under /usr or /nix/store.
Note
Non-systemd init systems get only the
/usr/bin/rustdesksymlink from the deb; no service is installed.apt purgeremoves/root/.config/rustdeskonly — user configs remain.
5.5 macOS
| Element | Value |
|---|---|
| App | /Applications/RustDesk.app (bundle id com.carriez.rustdesk) |
| LaunchDaemon | /Library/LaunchDaemons/com.carriez.RustDesk_service.plist → .../MacOS/service, KeepAlive, logs /var/log/rustdesk_service.err / .out |
| LaunchAgent | /Library/LaunchAgents/com.carriez.RustDesk_server.plist → RustDesk --server in LoginWindow and Aqua sessions |
| Install | Embedded install.scpt (admin prompt), copies user prefs to /var/root/Library/Preferences/com.carriez.RustDesk/ |
| Uninstall | Unloads daemon, removes plists, sets stop-service=Y |
| Update | update.scpt replaces the bundle with ditto, strips quarantine, reloads |
rustdesk --install-service on macOS only checks that both plists exist; install through the in-app Install button.
5.6 Docker
The repository Dockerfile builds a build container, not a runtime service:
| Item | Value |
|---|---|
| Base image | debian:trixie-slim |
| Toolchain | apt build deps, CMake 3.30.6 from source, vcpkg tag 2023.04.15 (libvpx libyuv opus aom), rustup default toolchain, Sciter libsciter-gtk.so |
| User | USER root at runtime (HOME=/home/user) |
| Entrypoint | entrypoint.sh: cd $HOME/rustdesk; copies Sciter lib into target/{debug,release}; --target X adds the rustup target; runs VCPKG_ROOT=/vcpkg cargo build --locked $@ |
docker build -t rustdesk-builder .
docker run --rm -it -v "$PWD":/home/user/rustdesk \
-v rustdesk-git-cache:/home/user/.cargo/git \
-v rustdesk-registry-cache:/home/user/.cargo/registry \
rustdesk-builder --release
Finished release [optimized] target(s)
Caution
There is no
EXPOSE,VOLUMEorCMD; the image does not run RustDesk or any server. Output files are root-owned;PUID/PGIDfrom the README are ignored. The image pins vcpkg2023.04.15, not the CI baseline, and builds only the legacy Sciter binary.
5.7 Building from source (native)
git clone --recurse-submodules https://github.com/rustdesk/rustdesk
cd rustdesk
export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus aom
python3 build.py --flutter --hwcodec --unix-file-copy-paste
(Debian/Ubuntu host: produces rustdesk-1.5.0.deb in the repository root)
| Host | build.py output |
|---|---|
Windows + --flutter |
rustdesk-<ver>-install.exe (portable packer) |
| Windows Sciter | rustdesk-<ver>-win7-install.exe |
Debian/Ubuntu + --flutter |
rustdesk-<ver>.deb (rustdesk-unattended-wayland-<ver>.deb with --drm) |
| Arch (pacman) | rustdesk-<ver>-manjaro-arch.pkg.tar.zst |
| Fedora (yum) / openSUSE (zypper) | Sciter-style rustdesk-<ver>-fedora28-centos8.rpm / -suse.rpm (no Flutter path) |
macOS + --flutter |
RustDesk.app (no dmg; dmg made by CI or res/osx-dist.sh) |
Cargo features: flutter, hwcodec, vram (Windows), mediacodec (Android), drm, drm-wake, linux-pkg-config, unix-file-copy-paste, screencapturekit (macOS), inline (Sciter), resampler features use_dasp (default), use_samplerate, use_rubato. Release profile: lto = true, codegen-units = 1, panic = 'abort', strip = true.
The Flutter–Rust bridge is generated from src/flutter_ffi.rs:
cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid --locked
flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs \
--dart-output ./flutter/lib/generated_bridge.dart \
--c-output ./flutter/macos/Runner/bridge_generated.h
(generates src/bridge_generated.rs, flutter/lib/generated_bridge.dart and the C header; all git-ignored)
5.8 CI/CD
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
PR, push to master, manual | x86_64 Linux cargo build + cargo test --workspace (skips two cursor/key-state tests) |
flutter-ci.yml |
PR, push, manual | Full Flutter build without uploads |
flutter-nightly.yml |
daily 00:00 UTC, manual | Full build, publishes the nightly release |
flutter-tag.yml |
tags vX.Y.Z / vX.Y.Z-N |
Full build, publishes the tag release |
flutter-build.yml |
reusable | SBOM (syft CycloneDX), bridge, Windows x64/arm64 + x86 Sciter, macOS x64/arm64 dmg, Android per-ABI + universal APK, Linux deb/rpm/Arch, unattended-Wayland deb, Sciter debs, AppImage, Flatpak; iOS lib only; web job disabled |
bridge.yml |
reusable | flutter_rust_bridge 1.80.1 codegen |
third-party-RustDeskTempTopMostWindow.yml |
reusable | Builds the privacy-mode topmost-window helper |
fdroid.yml |
tags, manual | Publishes rustdesk-version.txt (versionCode = X·10⁶+Y·10⁴+Z·10²+A) |
update-webpki-roots.yml |
Mondays 03:00, manual | PR bumping webpki-roots |
clear-cache.yml, playground.yml |
manual | Cache purge / experiments |
Signing: Windows binaries are sent to a signing task server with res/job.py (BASE_URL, SECRET_KEY, SIGN_TIMEOUT, TIMEOUT), only when the SIGN_BASE_URL secret exists; macOS uses codesign + rcodesign notary-submit --staple; Android uses r0adkll/sign-android-release.
6. Security
6.1 Threat model summary
| Asset | Protection | Residual risk |
|---|---|---|
| Session content | Ed25519-authenticated X25519/secretbox E2E encryption, also through relays | Silent downgrade to unencrypted if key verification fails (user prompt only); direct IP never E2E |
| Host access | Password challenge-response, approve mode, 2FA, IP/ID whitelists, per-feature permissions | In-memory lockout counters; 6-char default one-time password |
| Stored secrets | Salted SHA-256 password hash; secretbox with machine-UUID key | Obfuscation against local attackers who can read config and machine UUID |
| Metadata | Self-hosted server option | Public server sees IDs, public keys, IPs, NAT types |
| Policy | Custom-client signing, server strategies | Server strategy push can change any option |
6.2 Authentication
Password verification. The host sends Hash{salt, challenge}; the client replies with SHA256(SHA256(password + salt) + challenge), compared in constant time. Temporary passwords are checked first (if enabled), then the permanent password (if enabled, or if built-in allow-logon-screen-password applies at a lock/logon screen).
| Rule | Value |
|---|---|
| Temporary password alphabet | 23456789abcdefghijkmnpqrstuvwxyz (no 0 1 l o); digits only with allow-numeric-one-time-password |
| Temporary password length | 6 / 8 / 10 (default 6) |
| Rotation | After every successful authorization; after 10 consecutive wrong attempts |
| Permanent password storage | "01" + encrypted(SHA256(password + salt)), 32-char salt; legacy plaintext accepted |
| Permanent password complexity | Flutter UI only: digit + upper + lower, length ≥ 8 (CLI enforces none) |
| Max stored length | 128 characters |
| Changing the permanent password | Clears trusted devices |
| Recent-session reconnect | Same session key within 30 s is re-authorized without prompting |
Rate limiting (per process, in memory):
| Bucket | Threshold | Message | Audit alarm |
|---|---|---|---|
| Per IP, per minute | > 6 | "Please try 1 minute later" | SixAttemptsWithinOneMinute |
| Per IP, total | > 30 | "Too many wrong attempts" | ExceedThirtyAttempts |
| IPv6 /64, /56, /48 | > 60, > 80, > 100 | "Too many wrong attempts for IPv6 prefix /N" | ExceedIPv6PrefixAttempts |
| Windows terminal OS login | from 3rd failure: 15 s × 2ⁿ⁻³, max 30 min, reset after 120 min idle | "Please try again in N second(s)." | TerminalOsLoginBackoff |
Two-factor authentication. TOTP (SHA1, 6 digits, 30 s step, skew 1, issuer "RustDesk Connection", account = device ID). The secret is stored encrypted in option 2fa. With a Telegram bot token (option bot), each 2FA-gated login sends the current code and the source IP to https://api.telegram.org/bot<token>/sendMessage. Trusted devices (hwid = SHA-256 of machine UID, plus ID, name and platform) skip 2FA for 90 days.
Account login (API). OIDC via /api/oidc/auth and polling /api/oidc/auth-query (1 s interval, 180 s max); username/password via /api/login. With "remember me", access_token and user_info are stored as local options.
6.3 Authorization and access control
Order of checks for an incoming session:
- Admission control (64 unauthenticated total, 16 per IP).
- IP whitelist → "Your IP is blocked by the peer" + alarm.
allow-only-conn-window-open→ "The main window is not open".- Challenge sent; connection audit posted.
- Login type latched (a different type later → "Connection not allowed").
- Trusted-device check (may clear 2FA).
- ID whitelist → "Your ID is blocked by the peer" (failure-counted).
- Per-type permission (file, camera, terminal, tunnel).
- Privacy mode already active → refused.
- Username must equal this device's ID (or direct IP) → else "Offline".
- Approve mode / password.
- 2FA, then authorize.
Permission resolution. A server-pushed ControlPermissions bit pair (2 bits per permission: 1 = disable, 2 = enable) wins; otherwise the local enable-* option; on Flutter desktop access-mode=full forces everything on and view forces everything off. The Connection Manager can toggle keyboard, clipboard, audio, file, restart, recording, block-input and privacy-mode live.
Session scope. After authorization, messages outside the session type are logged; allow-scope-violation-alarm posts an alarm and allow-scope-violation-close closes the session.
Warning
allow-remote-config-modificationis enforced only by the host's Flutter settings UI (it blocks the settings page while remote input is detected). It does not gate protocol-level actions. The API server's heartbeatstrategy.config_optionscan set any option key, including whitelists, approve mode and permissions — protect the API server accordingly.
6.4 Encryption and key exchange
| Stage | Mechanism |
|---|---|
| Device identity | Ed25519 key pair generated once, stored in RustDesk.toml |
| Client ↔ hbbs | KeyExchange signed by the server key; kx-v2 signed params (rdkx-params domain); secretbox after exchange; tolerated absence on old hbbs unless required |
| Peer ↔ peer | Host sends SignedId{id, ephemeral box pk, dtls_fingerprint, kx_version} signed by its Ed25519 key; the controller verifies it using the host key vouched for by hbbs, sends a box-sealed random secretbox key |
| Cipher | XSalsa20-Poly1305 secretbox with per-direction counter nonces |
| kx v1 | Per-direction subkeys = BLAKE2b keyed hash over rdkx-spl, direction, versions and both ephemeral keys |
| WebRTC | DTLS; signed fingerprint must match or the controller aborts ("possible MITM") and falls back to relay |
| Fingerprint UI | Hex of the peer identity key, grouped by 4 characters |
| Failure | Behaviour |
|---|---|
| hbbs-supplied peer key missing/invalid | Logged "Handshake failed: invalid public key from rendezvous server"; continues unencrypted |
| Peer signature mismatch | "pk mismatch, fall back to non-secure"; host clears key_confirmed; unencrypted |
| Any of the above on WebRTC | Hard failure, relay fallback |
| Unencrypted non-direct session | Controller dialog "Insecure Connection" (conn-e2ee-unavailable-tip) — Continue or Close |
| Direct IP | Always unencrypted, no prompt |
Caution
Direct IP access (
direct-server=Y) listens on all interfaces on port 21118, performs no signed-key handshake and carries no server-side permissions. Enable it only on trusted networks, combine it with an IP whitelist, and prefer ID-based connections through your own server.
6.5 TLS / certificates
- HTTP API,
wss://and HTTPS proxies use rustls with bundledwebpki-rootsplus native OS roots, with a platform-verifier fallback; no revocation checking. - On failure, native-tls is tried; invalid certificates are accepted only with
allow-insecure-tls-fallback=Y(per host:port, cached). - The
*_strictHTTP client variants never fall back and refuse plainhttp://URLs ("Strict HTTP client requires HTTPS"). allow-websocket=Yskips RustDesk's own rendezvous key exchange and relies onwss; with anhttp://API server the WebSocket scheme is plainws://.webpki-rootsis refreshed weekly by an automated PR workflow.
6.6 Secrets at rest
| Item | Storage |
|---|---|
| Permanent password | Salted SHA-256, encrypted with secretbox keyed by the machine UUID (zero-padded to 32 bytes; device public key as fallback) |
| ID, socks password, unlock PIN, trusted devices, peer passwords, RDP/OS credentials | "00" + base64(version byte + 24-byte nonce + secretbox ciphertext), max 128 chars |
| 2FA secret, Telegram token | Same "00" format |
| Identity secret key | Config.key_pair in RustDesk.toml |
| RDP credentials on the controller | Windows Credential Manager via cmdkey /generic:localhost |
Anyone who can read the config files and obtain the machine UUID can decrypt these values; treat config directories as sensitive and restrict filesystem access.
6.7 Data sent to third parties by default
| Destination | Data | Condition |
|---|---|---|
rs-ny.rustdesk.com:21116 |
Device ID, machine UUID, identity public key, public IP/port, NAT type; per connection: target ID, NAT type, IPv6 address, WebRTC SDP | Public server in use |
rs-ny.rustdesk.com:21117 |
Relayed session bytes (E2E encrypted when secured) | Relay used |
https://api.rustdesk.com/version/latest |
OS, version, arch, device fingerprint | Not a custom client and enable-check-update ≠ N |
| STUN servers (Cloudflare, Google, antisip, Nextcloud) | STUN binding requests | WebRTC/IPv6 discovery |
<ipv4>.nip.io DNS |
Peer IPv4 in a DNS query | Connecting from IPv6 to an IPv4 target |
api.telegram.org |
2FA code + source IP | Only with a bot token |
Heartbeat, sysinfo, audit and switch-grant uploads are disabled when the API host is *.rustdesk.com.
6.8 Hardening checklist
- [ ] Deploy your own ID/relay/API server and distribute the key to every client
- [ ] Set
approve-modeandverification-methoddeliberately; avoid permanent passwords where attended support is enough - [ ] Enforce 2FA for unattended hosts; review trusted devices
- [ ] Use
whitelist(CIDR) for admin networks; keepdirect-serveroff - [ ] Disable unneeded permissions (
enable-tunnel,enable-terminal,enable-file-transfer,enable-camera) - [ ] Set
enable-lan-discovery=Non hosts where the LAN is untrusted (discovery replies reveal ID, MAC, hostname and logged-in username) - [ ] Set an unlock PIN; ship a signed custom client with
disable-settings/hide-*where users must not change policy - [ ] Keep
allow-insecure-tls-fallbackoff; usehttpsAPI servers - [ ] Protect the API server — its strategies can rewrite any client option
- [ ] Restrict read access to config directories; do not copy them between machines
7. Operations
7.1 Start, stop and status
| Platform | Start / stop | Status |
|---|---|---|
| Windows | sc start RustDesk / sc stop RustDesk; rustdesk --install-service / --uninstall-service |
sc query RustDesk |
| Linux | sudo systemctl start rustdesk / stop; rustdesk --install-service (privileged systemctl enable/start) |
systemctl status rustdesk |
| macOS | In-app Install / Stop service; launchctl on com.carriez.RustDesk_service |
`launchctl list |
| All | Tray Stop service (sets stop-service=Y) |
Status bar Ready |
sudo systemctl restart rustdesk
rustdesk --get-id
123456789
Tip
--uninstall-servicesetsstop-service=Y, which persists: a later reboot will not start the service. Use--install-service(or Start service) to clear it.
7.2 Health checks
| Check | How | Healthy |
|---|---|---|
| Registration | Status bar or log lines about registration | Ready |
| ID server reachability | TCP/UDP 21116, TCP 21115 | Open |
| Relay reachability | TCP 21117 | Open |
| Service running | sc query / systemctl status / launchctl list |
Running |
| Local IPC | rustdesk --get-id prints an ID |
ID printed |
| Hardware codec | Log Check hwcodec config, exit with: ... |
exit status 0 |
7.3 Logging
| OS | Location |
|---|---|
| Windows | %APPDATA%\RustDesk\log\<process> (service: C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\log) |
| Linux | ~/.local/share/logs/RustDesk/<process> |
| macOS | ~/Library/Logs/RustDesk/<process>; daemon stdout/stderr in /var/log/rustdesk_service.out / .err |
| Android | <home>/RustDesk/Logs |
Each process role logs to its own subfolder named after its first argument (server, service, tray, cm, portable-service; the main UI has none).
debugand above are written to file. The project's logging rule: anything that can fire per packet/frame/input must usetraceorhbb_common::throttled_log!.- Logs contain peer IPs and ports, device IDs, relay UUIDs, rendezvous hosts, NAT type and OIDC request results. No log statement prints passwords, tokens or keys.
- Heartbeat failures and Wayland portal errors are throttled (Wayland: repeat only when the cause changes or after 600 s).
Note
The session logs include source IPs ("Connection opened from <addr>", "direct access from <addr>"), so treat them as personal data under your retention policy.
7.4 Backup and restore
| What | Where | Notes |
|---|---|---|
| Identity (ID + key pair + permanent password) | RustDesk.toml |
Encrypted with the machine UUID — restores only on the same machine |
| Server config and security options | RustDesk2.toml |
Same caveat for encrypted fields |
| Peer list and remembered passwords | peers/*.toml |
Passwords encrypted per machine |
| UI preferences | RustDesk_local.toml, RustDesk_default.toml |
Portable |
Restore on the same machine:
sudo systemctl stop rustdesk
sudo cp -a backup/rustdesk/. /root/.config/rustdesk/
sudo systemctl start rustdesk
(no output; the status bar returns to Ready with the original ID)
Moving to new hardware: re-apply options with --config / --option or a custom client rather than copying encrypted files; set the ID with --set-id if you need to keep it (6–16 chars, letter first).
7.5 Upgrades
| Platform | Mechanism |
|---|---|
| Windows EXE install | allow-auto-update=Y (installed service) or the home-screen Update card; --update for custom clients |
| Windows MSI | New MSI (major upgrade); the updater re-creates the service with --import-config |
| macOS | Silent auto-update from the service binary; rustdesk --update [file.dmg]; deferred while a session is active |
| Linux | Package manager |
| Android | APK / F-Droid |
7.6 Capacity notes
- One encoder per display is shared by all viewers; a new viewer restarts the encoder so everyone gets a key frame.
- The slowest viewer gates the frame rate of a display (capture waits up to 3 s for all viewers to fetch a frame).
- Terminal: max 100 terminal services per host, 1 MB scrollback buffer each; idle cleanup after 1 h (non-persistent) or 2 h (persistent with no terminals).
- Port-forward mux: 256 channels per tunnel.
8. Performance Tuning
8.1 Quality, bitrate and FPS
| Knob | Scope | Default | Effect |
|---|---|---|---|
image_quality |
controller, per peer | balanced |
Ratio 1.5 (best) / 0.67 (balanced) / 0.5 (low) / custom×2/100 |
custom-fps |
controller | 30 | Server accepts 1–120; public server caps relayed sessions at 30 |
codec-preference |
controller | auto |
Auto: H.265 > H.264 > AV1 (if av1-test ≠ N) > VP9; VP8 on ≤ 4 GB RAM; majority vote across viewers |
i444 |
controller | off | 4:4:4 chroma for VP9/AV1 only; all viewers must want it |
enable-abr |
host | on | Adjust ratio every 3 s; N pins the ratio |
enable-hwcodec |
both | on | Hardware H.264/H.265 |
enable-directx-capture |
Windows host | on | DXGI vs GDI (GDI disables VRAM) |
allow-d3d-render |
Windows viewer | off | Needed for zero-copy VRAM decode |
allow-remove-wallpaper |
host | off | Fewer changed pixels |
Bitrate table (kbps, VP8/VP9 base, nearest preset scaled by pixels): 1280×720 = 1000, 1920×1080 = 2073, 2560×1440 = 3000, 3840×2160 = 5000, 7680×4320 = 12000. Hardware encoders multiply by a factor (2.0 for H.264, 1.5 for H.265 below 2000 kbps, decaying toward 1.0).
8.2 Video QoS constants
| Constant | Value |
|---|---|
| Default / min / max FPS | 30 / 1 / 120 |
| New viewer start FPS | 15 for the first second |
| Automatic floor | 5 FPS |
| Delay threshold | 150 ms above learned baseline |
| Congestion | 3 bad replies → drop ≤ 1/5; ≥ 1000 ms excess halves at once |
| ABR interval | 3 s; ×1.15 / ×1.1 / ×1.05 increases when the screen is dynamic |
| Ratio bounds | 0.1 (high resolution) / 0.2 – 40 |
| RTT baseline window | 20 samples |
8.3 Codec behaviour worth knowing
- Periodic keyframes are disabled (VPX
KF_DISABLED, hardware GOP = i32::MAX) except while recording (every 240 frames). - VAAPI encoders cannot change quality on the fly; quality changes restart the encoder.
- MediaCodec and VideoToolbox are not "latency free": the last frame is re-encoded up to 10 times when idle.
- The client marks a codec unsupported after 3 consecutive decode failures and renegotiates.
- Encoder creation failure falls back to VP9; hardware failures clear the cached hwcodec config.
8.4 Platform tips
| Symptom | Tuning |
|---|---|
| High CPU on host | Use hardware codec, lower FPS, choose Optimize reaction time |
| Blurry text | True color (4:4:4) with VP9/AV1, or Good image quality |
| Stutter on slow links | Keep ABR on; consider VP8 on weak hosts |
| Black screen with D3D | Turn off allow-d3d-render / texture rendering |
| Linux NVIDIA window closes | allow-always-software-render=Y + restart |
| High audio latency | Default systemd env sets PULSE_LATENCY_MSEC=60, PIPEWIRE_LATENCY=1024/48000 |
Tip
Audio encoding stops automatically after 800 silent packets (about 8 s), and idle desktops send no new video frames with VPX/AOM/VRAM encoders, so idle sessions cost little bandwidth.
9. Integrations
9.1 Self-hosted server HTTP API (client side)
| Method | Path | Purpose |
|---|---|---|
| POST | /api/heartbeat |
Every 3 s tick (≥ 15 s when idle): {id, uuid, ver, conns?, modified_at}; response may request sysinfo, disconnect connection ids, or push strategy.config_options |
| POST | /api/sysinfo / /api/sysinfo_ver |
CPU, memory, OS, hostname, username, version, preset address book/group/user/strategy/device fields |
| POST | /api/audit/conn, /api/audit/file, /api/audit/alarm |
Connection, file-transfer and alarm audits (retried up to 120 s; server dedups by nonce) |
| POST | /api/switch-grant |
Signed switch-sides grant |
| GET | /api/login-options |
OIDC providers |
| POST | /api/oidc/auth, GET /api/oidc/auth-query |
OIDC login |
| POST/GET | /api/login, /api/logout, /api/currentUser, /api/users, /api/peers, /api/device-group/accessible, /api/ab* |
Account, address book and group calls from the Flutter UI |
| POST | /api/devices/deploy |
rustdesk --deploy --token <t> [--id <id>] |
| POST | /api/devices/cli |
rustdesk --assign --token <t> ... |
| POST | /api/record |
Recording upload (code present, never enabled in 1.5.0) |
With builtin use-raw-tcp-for-api, API calls are tunnelled as HttpProxyRequest over an encrypted TCP connection to hbbs port 21116 (36 s timeout).
Deploy exit codes:
| Result | Printed | Exit |
|---|---|---|
OK |
Device deployed. |
0 |
NOT_ENABLED |
Server does not require deployment. |
3 |
INVALID_INPUT |
Invalid input. |
5 |
ID_TAKEN |
Id `<id>` is already used by another machine on the server. |
6 |
| other | error text | 1 |
sudo rustdesk --assign --token "$TOKEN" --device_group_name "Servers" --note "rack 4"
Done!
9.2 Server Pro management scripts (res/*.py)
All take a positional command plus --url <api base> and --token <bearer>; filters are fuzzy (%value%) unless - or already containing %; bulk operations on more than one match require typing Y.
| Script | Commands | Endpoints |
|---|---|---|
devices.py |
view, disable, enable, delete, assign (--assign_to type=value), filters --id --device_name --user_name --group_name --device_group_name --offline_days |
/api/devices, `/api/devices/{guid}/disable |
users.py |
view, disable, enable, delete, new, invite, enable-2fa-enforce, disable-2fa-enforce, disable-email-verification, reset-2fa, force-logout |
/api/users, /api/users/invite, /api/users/tfa/totp/enforce, /api/users/disable_login_verification, /api/users/force-logout |
user-groups.py |
view, add, update, delete, view-users, add-users |
/api/user-groups |
device-groups.py |
view, add, update, delete, view-devices, add-devices, remove-devices |
/api/device-groups |
strategies.py |
list, view, enable, disable, assign, unassign |
/api/strategies, /api/strategies/{guid}/status, /api/strategies/assign |
control-roles.py |
view, add, update, delete, enable, disable, view-users, assign-users, remove-users |
/api/control-roles |
admin-roles.py |
view, add, update, delete, view-users, add-users, remove-users |
/api/admin-roles |
audits.py |
view-conn, view-file, view-alarm, view-console (--conn-type 0 Remote Desktop, 1 File Transfer, 2 Port Transfer, 3 View Camera, 4 Terminal) |
`/api/audits/{conn |
ab.py |
address books, peers, tags, rules (`--rule-permission ro | rw |
python3 res/devices.py view --url https://rd.example.com --token "$TOKEN" --offline_days 30
(prints matching devices with last_online at least 30 days ago)
Warning
devices.pyandusers.pywithout filters act on every record the list call returns. Always runviewwith the same filters first.
9.3 Plugin / extension system
There is no runtime plugin loader in the 1.5.0 client. Extension points are:
| Mechanism | What you can change |
|---|---|
Signed custom client (custom.txt) |
App name, default/override options, built-in and hard settings |
Server strategies (heartbeat config_options) |
Any option on managed clients |
ControlPermissions from the server |
Per-session permission overrides |
ice-servers option |
Custom STUN/TURN servers for WebRTC |
rustdesk:// URI scheme and CLI launchers |
Integrate with ticketing / RMM tools |
| Remote printer driver | Windows printing to the controller |
9.4 Other integrations
- RDP: tunnels to
localhost:3389on the host and launchesmstscon the controller, credentials viacmdkey. - Wake-on-LAN: sent to MAC addresses from LAN discovery.
- Telegram: 2FA code delivery.
- Windows file clipboard: CLIPRDR (
FileGroupDescriptorW/FileContents); Linux FUSE mountscliprdr-client/cliprdr-serverbeside the IPC directory.
10. Developer Internals
10.1 Repository structure
| Path | Contents |
|---|---|
src/ |
Rust application: entry (main.rs, core_main.rs), client (client.rs, client/), server (server.rs, server/), rendezvous, IPC, platform, privacy mode, FFI, legacy Sciter UI, languages (lang/) |
src/server/ |
connection.rs (per-session state machine), video_service.rs, video_qos.rs, audio_service.rs, input_service.rs, clipboard_service.rs, display_service.rs, printer_service.rs, terminal_service.rs, terminal_helper.rs, portable_service.rs, uinput.rs, wayland.rs, rdp_input.rs, drm_capturer.rs, port_forward_mux.rs, login_failure_check.rs |
src/hbbs_http/ |
sync.rs (heartbeat/sysinfo/strategy), account.rs (OIDC), http_client.rs, downloader.rs, record_upload.rs |
src/platform/ |
windows.rs/windows.cc, macos.rs/macos.mm, linux.rs, gtk_sudo.rs, privileges_scripts/ (launchd plists, AppleScripts) |
libs/hbb_common |
Submodule: config.rs, password_security.rs, config/permanent_password.rs, protobuf protos/rendezvous.proto and message.proto, tcp.rs, udp.rs, proxy.rs, tls.rs, verifier.rs, websocket.rs, webrtc.rs, socket_client.rs, log_throttle.rs |
libs/base |
config/keys.rs (single import path for all option keys), fs.rs, keyboard.rs, platform/linux.rs, protos |
libs/scrap |
Capture (dxgi, quartz, x11, wayland, android), codecs (common/vpxcodec.rs, aom.rs, hwcodec.rs, vram.rs, codec.rs, record.rs, camera.rs, drmtap_dl.rs) |
libs/enigo, libs/clipboard, libs/virtual_display, libs/remote_printer, libs/portable, libs/libxdo-sys-stub |
Input, clipboard, IDD, printer, portable packer, libxdo stub |
flutter/ |
Flutter app: lib/desktop, lib/mobile, lib/common, lib/models, platform runners, test/ |
res/ |
Packaging (DEBIAN, rpm specs, PKGBUILD, msi, systemd unit, desktop files), vcpkg overlays/triplets, helper scripts |
appimage/, flatpak/, fastlane/ |
Other packaging and store metadata |
.github/workflows/ |
CI/CD |
AGENTS.md |
Contributor/agent rules (Rust, Tokio, logging, editing hygiene, localization) |
10.2 Protocol
Two protobuf files in libs/hbb_common/protos define the wire protocol:
| File | Key messages |
|---|---|
rendezvous.proto |
RegisterPeer, RegisterPk / RegisterPkResponse (OK, UUID_MISMATCH, ID_EXISTS, TOO_FREQUENT, INVALID_ID_FORMAT, NOT_SUPPORT, SERVER_ERROR, NOT_DEPLOYED), PunchHoleRequest / PunchHole / PunchHoleSent / PunchHoleResponse (ID_NOT_EXIST, OFFLINE, LICENSE_MISMATCH, LICENSE_OVERUSE), FetchLocalAddr / LocalAddr, RequestRelay / RelayResponse, TestNatRequest/Response, ConfigUpdate, KeyExchange, HttpProxyRequest, ControlPermissions, IdPk |
message.proto |
SignedId, PublicKey, Hash, LoginRequest / LoginResponse, PeerInfo / DisplayInfo, VideoFrame, AudioFrame, MouseEvent, KeyEvent (KeyboardMode Legacy/Map/Translate/Auto), Clipboard / MultiClipboards, FileAction / FileResponse, Misc (close reasons, permissions, privacy mode, audio format…), OptionMessage, SupportedDecoding |
Framing is length-prefixed (bytes_codec.rs); after the key exchange every frame is secretbox-sealed with a counter nonce.
10.3 Connection state machine (host)
accept socket -> admission control -> identity_handshake (SignedId / PublicKey)
-> Connection::start
-> on_open: IP whitelist, window-open check, send Hash{salt,challenge}, audit "new"
-> LoginRequest: latch type, trusted device, ID whitelist, per-type permission,
privacy check, username==id, approve mode / password, 2FA
-> authorize: rotate temp password, subscribe services (monitorN, audio, clipboard...)
-> session loop: input, clipboard, file jobs, options, permission toggles,
scope enforcement, idle / auto-disconnect timers
-> close: lock-after-session-end, release keys, restore resolution, audit
10.4 Video pipeline
capturer (DXGI | GDI | Magnifier | Quartz | X11 SHM | PipeWire | DRM | Android)
-> BGRA/texture frame -> (HDR->SDR on Windows) -> YUV (I420/NV12/I444)
-> encoder (VPX | AOM | hwcodec | VRAM | MediaCodec), ratio from video_qos
-> send_video_frame to subscribed connections -> wait for fetch (<= 3 s)
-> sleep remainder of spf; recorder taps frames if recording
Windows fallbacks: DXGI empty → GDI; >3 no-image tries → GDI; ACCESS_LOST restarts up to 3 times per 10 s before GDI; other DXGI errors → GDI.
10.5 Keyboard modes
| Mode | Client sends | Host injects |
|---|---|---|
| Map | Physical scan code position | Raw key by position (rdev; Wayland: evdev code + 8) |
| Translate | Characters from the local layout; dead keys skipped; numpad falls back to map | translate_keyboard_mode |
| Legacy | Control keys + characters with modifiers | enigo legacy_keyboard_mode |
Support: Legacy always; Map needs peer ≥ 1.2.0 and not Android; Translate/Auto need peer ≥ 1.2.0. Unsupported saved modes fall back to the first supported (Legacy). Windows Pause is always sent as Legacy.
10.6 Code patterns and conventions (from AGENTS.md)
- No
unwrap()/expect()in production code except tests and lock poisoning; preferResult+?. - Tokio: never nest runtimes or
block_onin async code; no locks held across.await;spawn_blockingfor blocking work. - Logging: per-event sites use
log::trace!orhbb_common::throttled_log!(interval, level, ...). - Imports: one braced
useper crate, split only for differing#[cfg]. - Minimal, additive diffs: new logic in feature modules; shared files (
src/core_main.rs,src/server/connection.rs,src/tray.rs) get one-line hooks. - Client-only code belongs in
libs/base;hbb_commonchanges require a server round-trip. - Localization:
src/lang/template.rsis the master key list (never edit for translations);en.rsonly overrides keys whose display text differs;it.rsis hand-maintained; new keys use sentence case and are appended to every language file. - Commits require a DCO sign-off (
git commit -s).
10.7 Module reference (selected functions)
| Module | Function | Purpose |
|---|---|---|
src/core_main.rs |
core_main() |
Parses argv, dispatches on args[0], returns args for the UI or None to exit |
src/rendezvous_mediator.rs |
RendezvousMediator::start_all() |
NAT test, sync, direct server, LAN listener, one registration task per server |
src/client.rs |
Client::_start() / connect() / secure_connection() |
Punch/relay race and E2E handshake |
src/client.rs |
handle_login_error(), check_if_retry() |
Maps login errors to dialogs; retry policy |
src/server.rs |
create_tcp_connection(), new() |
Admission + handshake; service registry |
src/server/connection.rs |
Connection::start(), validate_password(), check_failure() |
Session lifecycle, auth, rate limiting |
src/server/video_service.rs |
run() |
Capture/encode loop |
src/server/video_qos.rs |
VideoQoS |
FPS/ratio adaptation |
libs/scrap/src/common/codec.rs |
Encoder::update() |
Codec negotiation |
libs/scrap/src/common/hwcodec.rs |
check_available_hwcodec() |
HW probe |
src/hbbs_http/sync.rs |
start() |
Heartbeat, sysinfo, strategy application |
src/custom_server.rs |
get_custom_server_from_string() |
Exe-name / --config parser |
src/common.rs |
read_custom_client(), get_api_server(), get_key() |
Custom client, API URL, server key |
libs/hbb_common/src/config.rs |
Config::get_rendezvous_server(), option2bool() |
Server selection, boolean convention |
libs/base/src/fs.rs |
TransferJob |
File transfer engine |
src/virtual_display_manager.rs |
plug_in_headless() |
Virtual displays |
10.8 Testing Strategy
| Location | Kind | Run |
|---|---|---|
Inline #[cfg(test)] modules (~745 tests in ~102 files across src/ and libs/) |
Unit tests | cargo test --workspace |
src/server/video_qos/tests/*.rs (adaptation, baseline, invariants, jitter, recovery, robustness, sim, smoke, startup) |
QoS simulations | Part of cargo test |
src/client/tests/audio_state_tests.rs, audio_playback_recovery_tests.rs; src/audio_resampler/*tests.rs; src/server/audio_service/*_tests.rs |
Audio | Part of cargo test |
src/core_main.rs tests |
CLI routing | Part of cargo test |
flutter/test/*.dart (14 files: cursor, file model, input modifiers, quality monitor, server settings dialog, terminal…) |
Flutter | cd flutter && flutter test |
libs/scrap/examples, examples/ipc.rs |
Runnable examples | cargo run --example <name> |
cargo test --locked --workspace --no-fail-fast -- --skip test_get_cursor_pos --skip test_get_key_state
test result: ok. ... passed; 0 failed; ...
CI runs unit tests only on x86_64 Linux (default features), three cursor Flutter tests on macOS arm64 PR builds, and a DRM-feature subset in the unattended-Wayland job. cargo fmt, clippy and MSRV jobs are commented out. Project policy: a fix gets 1–3 regression tests that fail on master and pass with the fix; no new test infrastructure without maintainer approval.
Note
The
libs/hbb_commonsubmodule must be checked out (git submodule update --init --recursive); the workspace does not load without it.
11. Troubleshooting
Operator-focused items. End-user dialogs (wrong password, portal prompts, macOS permission cards) are covered in the User Guide.
Start with the log of the right process role: problems accepting sessions are in the server log, service start/stop in service, approval UI in cm, and outgoing sessions in the main UI log (no subfolder).
Tip
Many connection problems are transport-specific. Before deep debugging, retry with
<id>/r(forced relay): if the relayed session works, focus on NAT, UDP and firewall paths rather than authentication or capture.
11.1 Service and process
Error: ipc is occupied by another process, try kill it
Cause: A second --server could not bind the main IPC endpoint.
Solution:
- Stop the service, kill stray
rustdesk --serverprocesses, start the service again. - On Linux check
/tmp/RustDesk-<uid>/for stale sockets owned by another user.
Prevention: Do not run the Flutter main UI and a manually started --server for the same user.
Error: service stays stopped after reboot
Cause: stop-service=Y is persisted (tray Stop service, --uninstall-service, Linux service honours it at start and disables itself).
Solution: rustdesk --install-service, Start service, or rustdesk --option stop-service "" as admin.
Prevention: Hide the stop button with built-in hide-stop-service on managed fleets.
Error: Installation and administrative privileges required!
Cause: A management command (--password, --set-id, --config, --option, --assign, --deploy, --set-unlock-pin) was run on a portable copy or without elevation.
Solution: Run from an elevated prompt / sudo against the installed binary.
Prevention: Script management commands after installation.
Error: Settings are disabled!
Cause: Hard setting disable-settings=Y (custom client).
Solution: Use a build with built-in allow-command-line-settings-when-settings-disabled=Y, or change policy through the server strategy.
Prevention: Decide CLI manageability when building the custom client.
Error: --config does nothing
Cause: --config is silent on success and on parse failure; the string may be malformed.
Solution: Read back with rustdesk --option custom-rendezvous-server and --option key.
Prevention: Generate strings with the UI Export Server Config, or use host=...,key=....
Error: Failed to set Ctrl-C handler: <e>
Cause: Linux --service could not install its signal handler.
Solution: Check for unusual process supervision; restart the unit.
Prevention: Run under the shipped systemd unit.
Error: Failed to write plists: <e>
Cause: macOS service --write-plists could not write the launchd files.
Solution: Run with administrator rights; check /Library/LaunchDaemons permissions.
Prevention: Use the in-app Install flow.
Error: daemon.plist not found in embedded resources
Cause: Broken macOS app bundle.
Solution: Reinstall the app from the dmg.
Prevention: Do not modify the bundle contents.
Error: Unknown app directory of current exe file
Cause: macOS app launched from an unexpected location.
Solution: Move RustDesk into /Applications.
Prevention: Deploy to /Applications only.
11.2 Registration and server
Error: Server requires deployment. Run rustdesk --deploy --token <api_token> on this device.
Cause: The server replied NOT_DEPLOYED; registration is throttled to once per 30 s.
Solution:
sudo rustdesk --deploy --token "$API_TOKEN"
Device deployed.
Prevention: Include --deploy in your install script for servers that require deployment.
Error: Id <id> is already used by another machine on the server. (exit 6)
Cause: --deploy --id requested an ID owned by another device.
Solution: Choose another ID or free it on the server.
Prevention: Plan ID naming.
Error: Cannot deploy an unregistrable device! / Cannot deploy Outgoing-only clients.
Cause: Built-in register-device=N or hard setting conn-type=outgoing.
Solution: Use a different client build for devices that must register.
Prevention: Match custom-client type to device role.
Error: Rendezvous connection is timeout
Cause: No message from hbbs for 1.5 × keep-alive on a TCP/WebSocket mediator.
Solution: Check the path to hbbs. Behind nginx WebSocket proxying, set proxy_read_timeout above 60 s.
Prevention: Monitor hbbs and any reverse proxy.
Error: Handshake failed: invalid public key from rendezvous server
Cause: The configured key is not a valid 32-byte base64 Ed25519 key, or the hbbs-supplied peer key did not verify.
Solution: Correct the key option; sessions otherwise downgrade to unencrypted.
Prevention: Distribute keys via export/import or custom clients.
Error: Signature mismatch in key exchange
Cause: The hbbs key exchange signature does not verify against the configured key (wrong key or impostor server).
Solution: Verify the server's public key file and the client key.
Prevention: Pin the key in a signed custom client.
Error: the rendezvous server did not complete the key exchange
Cause: A WebRTC offer required a secured rendezvous channel but hbbs is too old.
Solution: Upgrade hbbs, or disable WebRTC (enable-webrtc=N).
Prevention: Keep server and clients in step.
Error: ID changed unexpectedly
Cause: hbbs answered UUID_MISMATCH; the client regenerates its ID (Config::update_id()).
Solution: Communicate the new ID or restore with --set-id.
Prevention: Avoid cloning machines with the same config; each install must have its own machine UUID.
Error: too many unauthenticated connections from <ip>
Cause: Admission control (16 per IP, 64 total) during a burst or scan.
Solution: Investigate the source IP; legitimate clients retry.
Prevention: Use IP whitelists on exposed hosts; keep direct IP access off.
11.3 Capture and codecs
Warning
On Wayland, RustDesk deliberately does not reset a capturer after repeated "no frame" results, because resetting would re-trigger the portal consent dialog every few minutes. A frozen Wayland picture therefore needs a manual reconnect or a service restart.
Error: dxgi error, fall back to gdi / No image, fall back to gdi
Cause: DXGI Desktop Duplication failed or produced no frames.
Solution: Usually self-healing; GDI is slower and disables VRAM encoding. Update GPU drivers; check enable-directx-capture.
Prevention: Keep GPU drivers current.
Error: dxgi access lost after 3 restarts in 10 seconds, fall back to gdi
Cause: Repeated ACCESS_LOST (mode changes, secure desktop, driver resets).
Solution: Restart the session; investigate display mode churn.
Prevention: Avoid frequent resolution changes during sessions.
Error: MIT-SHM extension not working properly on select X11 server
Cause: The X server's SHM extension is broken (common in some VNC/Xvfb setups).
Solution: Use an X server with working MIT-SHM.
Prevention: Test capture on headless X setups before rollout.
Error: Fatal pipewire error (process exits)
Cause: A PipeWire capture error starting with "Did not receive a reply".
Solution: Restart PipeWire / the desktop portal; the service restarts --server.
Prevention: Keep PipeWire and portals updated.
Error: drm capturer for display N unavailable (...); falling back to PipeWire
Cause: DRM/KMS capture could not serve a display (unattended-Wayland build).
Solution: Check /usr/lib/rustdesk/libdrmtap.so.0 is present; review libdrmtap dlopen failed logs.
Prevention: Install the complete rustdesk-unattended-wayland package.
Error: Failed to create encoder: <err>, fallback to VP9
Cause: The negotiated encoder could not be created.
Solution: Automatic; if hardware, check drivers or disable enable-hwcodec.
Prevention: Keep the hwcodec probe results fresh (they reset when the GPU signature changes).
Error: new hw encoder failed: ..., clear config
Cause: A cached hardware encoder failed at runtime.
Solution: The cache is cleared and VP9 is used; the next probe re-evaluates.
Prevention: Disable hardware codec on GPUs with known issues (#16324, #16156).
Error: Please change codec and try again.
Cause: A screenshot was requested from a VRAM (texture) frame repeatedly.
Solution: Switch the session codec to VP9/AV1 and retry.
Prevention: Use software codecs when screenshots matter.
Error: HDR output but the SDR white level cannot be read, assuming 80 nits until it can
Cause: Windows HDR desktop on a build older than 1709 or an API failure.
Solution: None required; colors may be dim.
Prevention: Keep Windows updated.
11.4 Audio
Error: Invalid audio capture layout: sample_rate=<r>, channels=<c>
Cause: The capture device reports an unusable layout.
Solution: Choose another Audio Input Device (audio-input).
Prevention: Standardize audio devices on hosts.
Error: Failed to read audio data:<err> (Linux)
Cause: The PulseAudio _pa IPC (hosted by the CM process) failed.
Solution: Ensure PulseAudio/PipeWire-Pulse is running for the user; per code comment, reinstalling pulseaudio may help.
Prevention: Keep a user session with audio running.
Error: no system audio from Windows 7 hosts
Cause: WASAPI loopback is event-driven and does not work before Windows 10 1703.
Solution: Select a microphone/virtual input device instead.
Prevention: Upgrade hosts.
11.5 Input
Error: Rejected connection on protected uinput ipc channel due to executable mismatch
Cause: A process that is not the same RustDesk binary tried to use the uinput IPC.
Solution: Investigate the caller; after an upgrade, restart the service so all processes run the new binary.
Prevention: Restart the service after package upgrades.
Error: X11 input does nothing
Cause: libxdo could not be loaded at runtime; the stub turns input into no-ops.
Solution: Install libxdo3/libxdo4 (xdotool on Arch/SUSE).
Prevention: Install from distribution packages that declare the dependency.
Error: Block input has no effect
Cause: block_input is a no-op on Linux and macOS.
Solution: Use privacy mode on macOS; there is no equivalent on Linux.
Prevention: Document platform limits for your support staff.
11.6 Windows installation and elevation
Error: Application name must match [a-zA-Z0-9-]+
Cause: Custom client app name contains invalid characters.
Solution: Rebuild the custom client with a valid name.
Prevention: Validate names during branding.
Error: <app> is not installed.
Cause: Update requested with no installed copy.
Solution: Install first.
Prevention: Check install state in deployment scripts.
Error: Failed to run the update msi: <e> / Unsupported update file format
Cause: Updater could not launch the MSI or got an unexpected file.
Solution: Update manually with the new MSI.
Prevention: Allow msiexec and the download host.
Error: Failed to run portable service process
Cause: The elevated/SYSTEM helper for quick support could not start (also no display available!, already running).
Solution: Install RustDesk instead of using portable elevation.
Prevention: Install on managed machines.
Error: Failed to connect to connection manager
Cause: The server could not reach the _cm IPC after launching the CM (10 retries).
Solution: Restart RustDesk in the user session; check that a desktop session exists.
Prevention: Keep users logged in for attended approval flows.
Error: Win10 or greater required!
Cause: Remote printer install on older Windows.
Solution: Upgrade the host.
Prevention: Check OS version before enabling printing.
11.7 Virtual display and privacy mode
Error: There are already 4 monitors plugged in.
Cause: Amyuni IDD limit reached.
Solution: Plug out a virtual display.
Prevention: Use Plug out all at session end.
Error: Plugging in too frequently.
Cause: Headless plug-in is rate-limited to once per 3 s.
Solution: Wait and retry.
Prevention: None.
Error: The virtual display is managed by other processes.
Cause: Another process controls the Amyuni driver.
Solution: Close the other process or reboot.
Prevention: Avoid multiple virtual display tools.
Error: Failed to install driver.
Cause: The Amyuni IDD driver (usbmmidd_v2\usbmmIdd.inf) could not be installed.
Solution: Run as administrator; check driver signing policy.
Prevention: Pre-approve the driver in enterprise policy.
Error: Privacy occupied by another one.
Cause: Another connection owns privacy mode.
Solution: Wait for it to be released.
Prevention: Coordinate admins.
Error: No privacy window created / No privacy monitor found
Cause: Topmost-window privacy mode failed (missing WindowInjection.dll, broker failure).
Solution: Switch to "Mode 2" (virtual display) on installed hosts.
Prevention: Keep installation files intact.
11.8 Terminal and tunnels
Error: Maximum number of terminal services (100) reached
Cause: Terminal service limit per host.
Solution: Close persistent terminals; idle ones expire after 1–2 h.
Prevention: Avoid leaving many persistent terminals.
Error: Terminal service user mismatch detected.
Cause: A persistent terminal service ID was reused by a different user/token.
Solution: Open a new terminal.
Prevention: Do not share terminal service IDs.
Error: Failed to get user token. / Failed to get current session id.
Cause: The Windows service could not obtain the console user's token.
Solution: Log a user on, or use administrator credentials.
Prevention: Keep a session for terminal-heavy workflows.
Error: Port forward target not authorized
Cause: A multiplexed tunnel channel tried to open a target different from the login target.
Solution: Create a separate mapping per target.
Prevention: One mapping per host:port.
11.9 API and account
Error: Heartbeat failed: ...
Cause: The API server is unreachable or rejects heartbeats (throttled log).
Solution: Check api-server, TLS, and proxy settings.
Prevention: Monitor the API endpoint.
Error: Strict HTTP client requires HTTPS: <url>
Cause: A strict call targeted an http:// URL.
Solution: Use an https:// API server.
Prevention: Terminate TLS on the API server.
Error: No authed oidc is found (repeating)
Cause: OIDC login pending; the client polls for up to 180 s.
Solution: Complete login in the browser.
Prevention: None.
Error: Downloader not found / Failed to get content length
Cause: Update download failures.
Solution: Retry or update manually.
Prevention: Allow the release host.
11.10 Build
Error: Couldn't find VCPKG_ROOT, also can't fallback to homebrew because it's only for macos aarch64.
Cause: VCPKG_ROOT not set.
Solution: Install vcpkg dependencies and export VCPKG_ROOT.
Prevention: Use the CI-pinned vcpkg commit.
Error: cargo build failed, please check rust source code.
Cause: Windows Flutter build: target/release/librustdesk.dll missing.
Solution: Fix the cargo error above it; run flutter clean if needed.
Prevention: Build with the pinned Rust toolchain.
Error: --drm is only supported on the deb packaging path
Cause: --drm on pacman/yum/zypper hosts or non-Linux.
Solution: Build the DRM variant on Debian/Ubuntu (#16220 tracks Flatpak/RPM).
Prevention: Use the deb path for unattended-Wayland.
Error: libdrmtap packaging gate fails (CPU-only stub / helper fallback)
Cause: libdrmtap built without EGL or with the privileged helper.
Solution: Install libegl-dev libgles2-mesa-dev (Arch: mesa libglvnd), delete third_party/libdrmtap/build-pkg, rebuild with -Dhelper=disabled.
Prevention: Build libdrmtap via build.py --drm.
Error: Sciter build crashes with Rust ≥ 1.78
Cause: i128 ABI change breaks Sciter.
Solution: Use Rust 1.75 (CI pin).
Prevention: Pin the toolchain.
Error: Android link fails on getifaddrs
Cause: Bionic exports it only from API 24; jniLibs target API 21.
Solution: Handled by build.rs compiling android_ifaddrs.c; ensure VCPKG_ROOT is set (build.rs panics otherwise).
Prevention: Build via flutter/ndk_*.sh.
Error: Unrecognized feature from build.py
Cause: -f/--feature has no available features in 1.5.0.
Solution: Omit -f.
Prevention: None.
12. Potential Improvements
| Area | Observation | Suggestion |
|---|---|---|
| Encryption downgrade | Non-WebRTC key failures continue unencrypted with a dismissable prompt; direct IP is never E2E | Offer a policy option to fail closed; add E2E for direct IP |
| Lockouts | Failed-login counters are in memory and reset on restart | Persist counters or add configurable thresholds |
| Secrets at rest | Encryption key is the machine UUID | Use OS keystores (DPAPI, Keychain, libsecret) where available |
| Strategy push | Server config_options can set any option |
Allow-list keys or require signed strategies |
| Complexity rules | Password rules exist only in the Flutter UI | Enforce the same rules in the core / CLI |
| CLI UX | No --help; --config and --option set are silent; flags must be first |
Add help text and success/failure output |
| Docker | Build image ignores PUID/PGID, runs as root, pins old vcpkg, builds only Sciter |
Align with CI and README |
| build.py | --portable and -f/--feature have no effect |
Remove or implement |
| Licence metadata | RPM specs say GPL-3.0; LICENCE is AGPL-3.0 | Correct spec files |
| Packaging | deb uses /usr/lib/systemd/system, rpm/Arch /etc/systemd/system; purge removes only root config |
Unify paths; document user-config cleanup |
| Recording upload | /api/record uploader exists but is never enabled |
Wire an option or remove dead code |
| Tests | CI unit tests only on x86_64 Linux; fmt/clippy/MSRV jobs commented out; Flutter tests mostly not run | Add Windows/macOS test jobs and full flutter test |
| Wayland | One capturer, no follow-window, consent persistence only in ScreenCast mode | Continue DRM path; broaden persistence |
| Android audio tip | Text says Android 10, code requires SDK 30 (Android 11) | Fix the translation key |
| Multi-server | Comments state multiple rendezvous servers are "not used any more" | Remove legacy multi-server code paths |
13. Quick Reference
13.1 Paths
| Item | Windows | Linux | macOS |
|---|---|---|---|
| Binary | C:\Program Files\RustDesk\rustdesk.exe |
/usr/bin/rustdesk → /usr/share/rustdesk/rustdesk |
/Applications/RustDesk.app |
| Config | %APPDATA%\RustDesk\config (service: C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config) |
~/.config/rustdesk (root: /root/.config/rustdesk) |
~/Library/Preferences/com.carriez.RustDesk |
| Logs | %APPDATA%\RustDesk\log |
~/.local/share/logs/RustDesk |
~/Library/Logs/RustDesk, /var/log/rustdesk_service.* |
| Service | RustDesk ("RustDesk Service") |
rustdesk.service |
com.carriez.RustDesk_service / _server |
| IPC | \\.\pipe\RustDesk\query* |
/tmp/RustDesk-<uid>/ipc* |
/tmp/RustDesk-<uid>/ipc* |
| Recordings | service: %SystemDrive%\ProgramData\RustDesk\recording; user: <Videos>\RustDesk |
<Videos>/RustDesk |
<Videos>/RustDesk (user video folder) |
13.2 Commands
rustdesk --version
rustdesk --get-id
sudo rustdesk --password 'S3cure-Pass-2026'
sudo rustdesk --set-id office-pc-01
sudo rustdesk --option custom-rendezvous-server rd.example.com
sudo rustdesk --option key <base64-key>
sudo rustdesk --config "host=rd.example.com,key=<base64-key>"
sudo rustdesk --deploy --token "$API_TOKEN"
sudo rustdesk --assign --token "$API_TOKEN" --user_name alice
rustdesk --install-service
rustdesk --uninstall-service
rustdesk --connect 123456789 --password abc123
1.5.0
123456789
Done!
Done!
(silent)
(silent)
(silent)
Device deployed.
Done!
13.3 Ports
| Port | Proto | Service |
|---|---|---|
| 21114 | TCP | API (default derived) |
| 21115 | TCP | NAT test |
| 21116 | TCP/UDP | ID/rendezvous |
| 21117 | TCP | Relay |
| 21118 | TCP | WS rendezvous; direct IP access |
| 21119 | TCP/UDP | WS relay; LAN discovery (UDP broadcast) |
13.4 Most-used option keys
| Key | Typical value |
|---|---|
custom-rendezvous-server |
rd.example.com |
relay-server |
rd.example.com |
api-server |
https://rd.example.com |
key |
server public key |
approve-mode |
password |
verification-method |
use-permanent-password |
whitelist |
10.0.0.0/8,192.168.1.0/24 |
enable-lan-discovery |
N |
enable-tunnel / enable-terminal |
N where not needed |
allow-auto-disconnect / auto-disconnect-timeout |
Y / 15 |
enable-hwcodec |
N on problematic GPUs |
stop-service |
empty (running) |