Comment blocks now stay within three lines and explain only the non-obvious. Long histories, include examples, and commented-out code are gone. No task, key, or value changed; the only data change is comment text inside the generated unit files.
RustDesk Server on Docker, behind Caddy
Deploys the self-hosted RustDesk server (OSS) on monoco: the
ID/rendezvous server hbbs and the relay server hbbr, both supervised by s6 inside the
official rustdesk/rustdesk-server-s6 container, reachable only through Caddy on
rust.tanshu.com:443. No RustDesk port is published on the host.
How the traffic is carried
RustDesk's native transports on 21115/21116/21117 are a custom TCP/UDP protocol, not HTTP,
so an HTTP reverse proxy cannot carry them. What is HTTP is the WebSocket transport that
hbbs and hbbr also listen on (21118 and 21119), and clients that enable WebSocket use
it for both rendezvous and relay. Caddy terminates TLS on 443 and forwards two paths into
the container over the Docker network:
| Path | Upstream | Purpose |
|---|---|---|
/ws/id |
rustdesk:21118 (hbbs) |
ID registration and signalling |
/ws/relay |
rustdesk:21119 (hbbr) |
Relayed sessions |
The code that decides this is in the client: hbb_common::websocket::check_ws maps a
domain host:21116 to wss://host/ws/id and host:21117 to wss://host/ws/relay, and
the path names are fixed there. Caddy sets X-Real-IP, which hbbs/hbbr trust to keep
the real client address for rate limiting and logs (upstream warns that these ports must
therefore never be exposed directly).
Settings
host: the public name,rust.tanshu.com. Both the Caddy site and the addresshbbsadvertises come from it.docker_container,image_name,image_version: container name and therustdesk/rustdesk-server-s6tag to run.data_location: host directory bind-mounted to/data(/var/lib/rustdesk/data), holding the SQLite database and the server key pair.rustdesk_relay_server: handed to clients byhbbsas the relay address. It keeps the public name, so clients rewrite it towss://rust.tanshu.com/ws/relay.rustdesk_publish_raw_ports/rustdesk_raw_ports: publish 21115/21116/21117 as well. Off by default, see "Clients that cannot use WebSocket" below.rustdesk_encrypted_only:ENCRYPTED_ONLY=1, sohbbrvalidates the server key too instead of accepting unkeyed clients.rustdesk_always_use_relay:ALWAYS_USE_RELAY=Y, forcing relayed sessions. WebSocket clients are relay-only anyway, so this only affects clients on the raw ports.
Prerequisites
- A DNS
Arecord forrust.tanshu.compointing atmonoco; Caddy issues the certificate over ports 80/443. - The Caddy container already running on
monoco(caddy/playbook.yml), with its Caddyfile at/var/lib/caddy/conf/Caddyfile.
Running this Playbook
The inventory in /etc/ansible/hosts already lists monoco, which is what hosts:
targets:
ansible-playbook playbook.yml
The playbook creates the rustdesk_net network, attaches Caddy to it, starts the
container, appends the site block to the Caddyfile, reloads Caddy, and prints the public
key.
Configuring the clients
On each client, open Settings → Network → ID/Relay Server and set:
| Field | Value |
|---|---|
| ID Server | rust.tanshu.com |
| API Server | https://rust.tanshu.com |
| Key | the printed value, i.e. /var/lib/rustdesk/data/id_ed25519.pub on the host |
then enable Settings → Network → Use WebSocket (allow-websocket=Y).
The API Server field looks odd for an OSS deployment, which has no API, but it is what
selects the TLS scheme: the client uses wss:// only when the API server starts with
https, and otherwise silently falls back to plaintext ws://rust.tanshu.com/... on port
80. Because the client also skips its own client↔server key exchange whenever WebSocket is
enabled — on the assumption that the transport is already encrypted — that fallback would
leave the connection unprotected rather than merely failing.
Trade-offs
- Relayed sessions only. With WebSocket enabled the client never hole-punches
(
force_relayis set, andhbbsskips the same-intranet path for WebSocket peers), so every session flows throughmonoco. Remote control works, but throughput is bounded by the server, and latency is higher than a direct connection. - Upstream labels this a Pro feature. The client's own tooltip says "RustDesk server OSS doesn't include this feature", even though the OSS binaries do listen on 21118/21119 and the OSS docs describe proxying them. Treat the WSS path as needing a real test: if clients fail to register, the fallback is the raw ports below.
- The address has to be the domain.
check_wsonly builds a TLS URL and the/wspaths for hostnames; an IP would becomews://ip:21118and bypass Caddy entirely.
Clients that cannot use WebSocket
Clients without the WebSocket option (older builds, third-party clients, or anything that
must connect directly) need the native ports. Set rustdesk_publish_raw_ports: true and
re-run the playbook, then open 21115-21116 (TCP) plus 21116/udp and 21117 (TCP) on the host
and in any cloud firewall:
ufw allow 21115:21117/tcp
ufw allow 21116/udp
WebSocket clients keep working at the same time; they ignore the raw ports.
Notes
- Logs.
docker logs rustdeskcovers both services. - Server key. If
/var/lib/rustdesk/data/id_ed25519is lost, every client has to be reconfigured with the new public key. Back it up. - Upgrading. Re-running the playbook pulls the tag in
image_versionand recreates the container; the key pair and database live indata_locationand survive.