Self-HostingDocker

RustDesk Server with Docker: Your Own Self-Hosted Remote Desktop (Linux, Windows, Mac)

Before you start

  • 15 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

RustDesk server is a free, open-source pair of small programs (hbbs and hbbr) that lets you run remote desktop connections through your own server instead of the public RustDesk one.

RustDesk official logo

Logo: the official RustDesk project (github.com/rustdesk/rustdesk, AGPL-3.0)

You keep control of who can connect and where the connection data goes. It is the "TeamViewer, but mine" setup: the RustDesk apps on your computers stay the same, only the address book behind them is yours.

At a glance#

  • What you get: your own ID server (hbbs) and relay server (hbbr), running in two small containers.
  • Time needed: about 20 minutes, plus a few minutes per computer you add.
  • Difficulty: easy to medium. The only tricky part is opening the right ports.
  • You need: Docker with the Compose plugin, an always-on machine (a cheap VPS, a mini PC or a Raspberry Pi is enough), and the RustDesk app on two computers to test.
WhatValue
Current server version1.1.16, released 20 Jul 2026 (GitHub releases)
Pinned image (official, Docker Hub)rustdesk/rustdesk-server:1.1.16
Ports21115, 21116 (TCP and UDP), 21117; optional 21118 and 21119 for the web client
Image sizeabout 5.9 MB compressed (amd64, Docker Hub)
Official docsrustdesk.com/docs, Server OSS

Why this version? We checked the GitHub issues and forum posts. We found no reports of failed installs or lost data for 1.1.16. Two open issues are worth knowing (see Known bugs): they affect the new 1.5.0 client in special cases. The previous release 1.1.15 had a lag report, so do not roll back to it. If you must roll back, use 1.1.14.

How it fits together#

Both computers register with hbbs, which introduces them. If they cannot connect directly, hbbr passes the screen between them.

How a RustDesk connection works: the remote PC registers its ID with hbbs, your laptop asks hbbs for that ID, hbbs saves keys and peers in the data folder and, if no direct link is possible, points both sides to the hbbr relay

Remember three things:

  1. hbbs is the phone book. It only knows who is online and where.
  2. hbbr is the relay. It carries the screen only when a direct link fails, so it uses the bandwidth.
  3. The key (id_ed25519.pub) is what your clients check. It makes sure they talk to your server, and, once forced, that nobody else uses your relay.

Before you start#

You need Docker on the server machine and a way to open ports to it.

SystemServer hostNotes
LinuxDocker Engine + Compose pluginBest choice. network_mode: host shows real client IPs
WindowsDocker Desktop (WSL2 backend)Fine for testing. No host networking, so you map ports
MacDocker DesktopSame as Windows

Check your tools:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Ports 21115, 21116 (TCP and UDP) and 21117 are free on the server
  • [ ] You know the server's address (IP or a name) that your clients will use
  • [ ] You can open those ports in the firewall, or you will use WireGuard or Tailscale
  • [ ] You backed up any config you are about to replace

Hardware is tiny. The official docs say a basic cloud server is enough, even a Raspberry Pi. Relay traffic is what costs bandwidth: roughly 100 KB/s for office work and up to 3 MB/s for a full HD screen with lots of motion.

Tested on 2026-10-26: not run. Commands checked against the official RustDesk docs and the repository files.

Step 1: Start hbbs and hbbr#

You create a folder, save one Compose file and start it. Data, including your key pair, lives in ~/docker_volumes/rustdesk/data.

The -k _ on both services is the important part: it makes both load (or create) your key pair and refuse clients without it. The hbbr default is to accept anybody.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/rustdesk/data
cd ~/docker_volumes/rustdesk
nano docker-compose.yml
File contentyaml
services:
  hbbs:
    container_name: hbbs
    image: rustdesk/rustdesk-server:1.1.16
    command: hbbs -k _
    volumes:
      - ./data:/root
    network_mode: host
    depends_on:
      - hbbr
    restart: unless-stopped

  hbbr:
    container_name: hbbr
    image: rustdesk/rustdesk-server:1.1.16
    command: hbbr -k _
    volumes:
      - ./data:/root
    network_mode: host
    restart: unless-stopped
Run in terminalbash
docker compose up -d

Open the ports (example for UFW; use your own firewall tool if you have another):

Run in terminalbash
sudo ufw allow 21115:21117/tcp
sudo ufw allow 21116/udp

Windows (PowerShell)#

Docker Desktop has no host networking, so this file maps the ports. Replace <your-server-address> with the IP or name your clients will use.

Codepowershell
$d = "$HOME\docker_volumes\rustdesk"
New-Item -ItemType Directory -Force "$d\data" | Out-Null
Set-Location $d
notepad docker-compose.yml
File contentyaml
services:
  hbbs:
    container_name: hbbs
    image: rustdesk/rustdesk-server:1.1.16
    command: hbbs -r <your-server-address>:21117 -k _
    ports:
      - "21115:21115"
      - "21116:21116"
      - "21116:21116/udp"
    volumes:
      - ./data:/root
    depends_on:
      - hbbr
    restart: unless-stopped

  hbbr:
    container_name: hbbr
    image: rustdesk/rustdesk-server:1.1.16
    command: hbbr -k _
    ports:
      - "21117:21117"
    volumes:
      - ./data:/root
    restart: unless-stopped
Run in terminalpowershell
docker compose up -d

Other computers on your network can only reach the server if Windows Defender Firewall allows it. In an administrator PowerShell:

Codepowershell
New-NetFirewallRule -DisplayName "RustDesk server TCP" -Direction Inbound -Protocol TCP -LocalPort 21115-21117 -Action Allow
New-NetFirewallRule -DisplayName "RustDesk server UDP" -Direction Inbound -Protocol UDP -LocalPort 21116 -Action Allow

Mac (Terminal)#

Same Compose file as Windows (ports mapped, with -r):

Run in terminalbash
mkdir -p ~/docker_volumes/rustdesk/data && cd ~/docker_volumes/rustdesk
nano docker-compose.yml   # paste the file from the Windows tab, save
docker compose up -d

macOS may ask whether Docker may accept incoming connections. Say yes.

Check it worked#

Run in terminalbash
docker compose ps
docker compose logs --tail 20
cat data/id_ed25519.pub

Expected: both containers are running, and id_ed25519.pub holds one line of letters and numbers. That line is your public key. On Windows use Get-Content data\id_ed25519.pub.

Potential issue: if hbbs and hbbr start in the very same second on a fresh folder, they could each write a key. If clients later say "invalid key", run docker compose down, delete the data folder, start hbbs alone first (docker compose up -d hbbs), then docker compose up -d.

Step 2: Set up the clients#

Each RustDesk app needs three things: the ID server, the key, and (optional) the relay. Install the app first: rustdesk.com or your package tool.

Linux#

Run in terminalbash
flatpak install flathub com.rustdesk.RustDesk

Or download the .deb or .AppImage from the client releases.

Windows (PowerShell)#

Run in terminalpowershell
winget install RustDesk.RustDesk

Mac (Terminal)#

Run in terminalbash
brew install --cask rustdesk

Then, on every computer (these steps are the same everywhere, from the official client docs):

  1. Open RustDesk. Click the menu button (three dots) next to your ID, then Network.
  2. Click Unlock network settings (it needs administrator rights).
  3. ID Server: your server address, for example <your-server-address>. A port is optional (:21116).
  4. Relay Server: leave empty. RustDesk works it out from the ID server.
  5. API Server: leave empty. It is for Server Pro only.
  6. Key: paste the single line from id_ed25519.pub.
  7. Click Apply.

Do it once, then use Export Server Config on that computer and Import Server Config on the others to save typing.

Check it worked#

At the bottom of the RustDesk window the status says Ready (not "Connecting to the RustDesk network..."). Enter the ID of the other computer, connect, and accept the connection there.

Reaching it from outside#

Pick the smallest door that works. Do not open more than you need.

OptionWhat is open to the internetGood for
WireGuard or TailscaleNothing (the VPN port only)Best. Use the server's VPN address as the ID server
Forward only the needed ports21115, 21116 TCP+UDP, 21117A public server, with the key forced
Everything open (21114 to 21119)Too muchNever. 21114 is Pro only, 21118 and 21119 are for the web client

With a VPN, every client must be on the VPN too. That is the cost, and the safety.

If you forward ports, forward exactly: TCP 21115, 21116 and 21117, plus UDP 21116. Keep 21118 and 21119 closed unless you read the web client note below.

Put it behind HTTPS (with or without Traefik)#

Short and honest: the RustDesk ID and relay traffic is not HTTP, so Traefik and Caddy cannot sit in front of it. This section only applies to the optional web client.

hbbs and hbbr speak RustDesk's own protocol on ports 21115 to 21117 (TCP and UDP). It is already encrypted with your server key, and a web reverse proxy cannot carry it. So for the normal setup, keep it safe like this:

  • Force the key (-k _, done in Step 1), so only clients with your key can use the server.
  • Open only the needed ports, or use WireGuard or Tailscale.
  • Do not publish 21114, 21118 or 21119 unless you use the web client.

The only part that is web traffic is the WebSocket pair for the web client: hbbs on 21118 and hbbr on 21119. Official docs warning: hbbs and hbbr trust the X-Real-IP and X-Forwarded-For headers on those ports without checking them. Anyone who reaches 21118 or 21119 directly can fake their IP address. So expose them only through a reverse proxy that sets X-Real-IP itself, and block 21118 and 21119 in the firewall for everyone else.

The official Server Pro FAQ proxies /ws/id to port 21118 and /ws/relay to port 21119 on HTTPS. We use the same paths. We did not test the web client ourselves (VERIFY with your client).

With Traefik#

Use this only if you want the web client and already run Traefik for a domain. Traefik runs in its own container, so it cannot use the host-network file. Use the mapped-ports file from the Windows tab, add the proxy network and these labels:

Run in terminalbash
docker network create proxy
File contentyaml
services:
  hbbs:
    container_name: hbbs
    image: rustdesk/rustdesk-server:1.1.16
    command: hbbs -r <your-domain>:21117 -k _
    ports:
      - "21115:21115"
      - "21116:21116"
      - "21116:21116/udp"
    volumes:
      - ./data:/root
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.rd-id.rule=Host(`<your-domain>`) && PathPrefix(`/ws/id`)
      - traefik.http.routers.rd-id.entrypoints=https
      - traefik.http.routers.rd-id.tls=true
      - traefik.http.services.rd-id.loadbalancer.server.port=21118
    restart: unless-stopped

  hbbr:
    container_name: hbbr
    image: rustdesk/rustdesk-server:1.1.16
    command: hbbr -k _
    ports:
      - "21117:21117"
    volumes:
      - ./data:/root
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.rd-relay.rule=Host(`<your-domain>`) && PathPrefix(`/ws/relay`)
      - traefik.http.routers.rd-relay.entrypoints=https
      - traefik.http.routers.rd-relay.tls=true
      - traefik.http.services.rd-relay.loadbalancer.server.port=21119
    restart: unless-stopped

networks:
  proxy:
    external: true

The labels are our example, not from the official docs (VERIFY against your Traefik setup). Ports 21118 and 21119 are deliberately not published; Traefik reaches them over the proxy network.

Without Traefik: Caddy#

Choose Caddy if you have no Traefik and want the web client over HTTPS on one small server.

CaseResultYou do
Public domainAutomatic Let's EncryptDNS A record to the server, ports 80 and 443 open
Home onlytls internal (local certificate)Trust Caddy's root certificate on each device

Caddyfile for a public domain (Caddy runs on the same machine as the Linux host-network file, so it can use localhost):

File contenttext
rustdesk.example.com {
    @id path /ws/id
    @relay path /ws/relay
    reverse_proxy @id localhost:21118 {
        header_up X-Real-IP {remote_host}
    }
    reverse_proxy @relay localhost:21119 {
        header_up X-Real-IP {remote_host}
    }
}

Caddyfile for home only:

File contenttext
rustdesk.home.arpa {
    tls internal
    @id path /ws/id
    @relay path /ws/relay
    reverse_proxy @id localhost:21118 {
        header_up X-Real-IP {remote_host}
    }
    reverse_proxy @relay localhost:21119 {
        header_up X-Real-IP {remote_host}
    }
}

Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):

Run in terminalbash
# Linux: on the Caddy machine
sudo caddy trust
# other Linux devices, with root.crt copied over
sudo cp root.crt /usr/local/share/ca-certificates/caddy-root.crt && sudo update-ca-certificates
# Windows (PowerShell as administrator)
certutil -addstore -f ROOT root.crt
# Mac
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt

Run Caddy (Linux: install from the official apt repository on caddyserver.com/docs/install first):

Run in terminalbash
# Linux
sudo systemctl reload caddy
# Windows (PowerShell)
winget install CaddyServer.Caddy
caddy run
# Mac
brew install caddy && caddy run

Or use the Docker image. On a host-network Linux setup the container must share the host network too, so localhost means the host. Save this in ~/docker_volumes/caddy next to the Caddyfile above:

File contentyaml
services:
  caddy:
    image: caddy:2.11.7
    container_name: caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy_data:/data
      - ./caddy_config:/config

Caddy passes WebSockets by default. Block 21118 and 21119 for outside visitors (for example sudo ufw deny 21118/tcp and sudo ufw deny 21119/tcp): Caddy reaches them over the local machine.

Check it worked: curl -I https://rustdesk.example.com/ws/id answers with an HTTP status from hbbs (a plain request is not a WebSocket, so an error code such as 400 still proves Caddy reached it). The real test is the web client. The desktop apps never use this address.

Security and testing#

Keep the key forced, keep the ports few, keep the private key private.

  • Never share id_ed25519 (the private key). Only id_ed25519.pub goes to clients. Lock the folder:
Run in terminalbash
chmod 700 ~/docker_volumes/rustdesk/data
chmod 600 ~/docker_volumes/rustdesk/data/id_ed25519
  • Test that the key is forced. On a computer with an empty Key field and your ID server set, the status should not become Ready (an invalid-key error or "Connecting..." forever). With the right key it becomes Ready.
  • Set a permanent password on each remote computer in the RustDesk app and use "Unattended access" only where you need it. The server does not replace access control on the client.
  • Pin the image tag. The official docs use latest; we pin so updates are your decision.
  • Fail2ban or CrowdSec: release 1.1.15 added log lines for failed authentication that these tools can read. See docs/environment-variables.md in the repository for the options.
  • Server Pro or open source? The open-source server has no accounts, web console, audit log or custom clients. If you want those, look at Server Pro. This guide covers only the free server.

Tests:

Run in terminalbash
docker compose ps
docker logs hbbs --tail 50
docker logs hbbr --tail 50

Expected: both running, no repeating errors.

Troubleshooting#

ProblemSolution
Client shows "Connecting to the RustDesk network..." foreverCheck the ID server address, the key, and that TCP 21115/21116 and UDP 21116 are open
Key error ("invalid key" style message)The key in the app must equal id_ed25519.pub. Re-copy it. Do not delete the key files
Connects even with no key-k _ is missing on hbbr (or hbbs). Add it and run docker compose up -d
New key after every restartThe ./data:/root volume is not mounted. Check docker compose config
"Failed to connect to relay server"TCP 21117 is blocked, or the relay address is wrong. Open 21117 or set the Relay Server field
Everything goes through the relay, nothing directUDP 21116 is closed. Open it
Client IP in logs shows 172.17.0.1Bridge networking hides real IPs. Use host networking on Linux
Windows or Mac: peers cannot reach the serverSet -r <your-server-address>:21117 and allow the ports in the local firewall
Two computers in the same home cannot connect through the public addressRouter "NAT loopback" problem. Use the server's local address on the local computers

Known bugs#

Open on GitHub at the time of writing (read 2026-10-26):

  • #733: one report of hbbs 1.1.16 not answering a RustDesk 1.5.0 client behind carrier-grade NAT, so that client never appears online. Other clients are fine. If you hit it, try the previous client version.
  • #734: a proposed change that adds the 1.5.0 client's new key exchange, WebRTC and UDP hole punching to hbbs. Until it ships, 1.5.0 clients fall back to the older paths.

Diagnostics:

Run in terminalbash
docker logs hbbs --tail 100
docker logs hbbr --tail 100
docker compose ps

Updates and backups#

Update by changing the tag, back up by copying the data folder. The data folder holds your key pair and the SQLite database (db_v2.sqlite3). Lose the private key and every client needs a new key.

Update (Linux/Mac; on Windows use the same docker compose lines in PowerShell):

Run in terminalbash
cd ~/docker_volumes/rustdesk
# read the release notes, edit the image tags in both services, then:
docker compose pull && docker compose up -d

Backup script (Linux/Mac). It stops the stack briefly so the database is consistent:

Run in terminalbash
mkdir -p ~/docker_volumes/rustdesk/backups
cd ~/docker_volumes/rustdesk
docker compose down
tar czf backups/rustdesk-$(date +%F).tgz data
docker compose up -d

Windows (PowerShell):

Codepowershell
Set-Location "$HOME\docker_volumes\rustdesk"
docker compose down
New-Item -ItemType Directory -Force backups | Out-Null
Compress-Archive -Path data -DestinationPath "backups\rustdesk-$(Get-Date -Format yyyy-MM-dd).zip"
docker compose up -d

Schedule: Linux/Mac cron at 03:00 (0 3 * * * <path-to-script>). Windows: Task Scheduler running the PowerShell lines.

Restore test#

Do this once, before you need it. It starts a copy of the server on other ports and checks the key is the same:

Run in terminalbash
mkdir -p /tmp/rd-restore && tar xzf ~/docker_volumes/rustdesk/backups/rustdesk-*.tgz -C /tmp/rd-restore
diff /tmp/rd-restore/data/id_ed25519.pub ~/docker_volumes/rustdesk/data/id_ed25519.pub && echo "same key"
docker run --rm -d --name rd-restore -v /tmp/rd-restore/data:/root -p 22115:21115 -p 22116:21116 -p 22116:21116/udp rustdesk/rustdesk-server:1.1.16 hbbs -k _
docker logs rd-restore --tail 20

Expected: same key and no errors in the log. Then clean up:

Run in terminalbash
docker stop rd-restore && rm -rf /tmp/rd-restore

Performance and resources#

The server is very light. The image is about 5.9 MB compressed. We did not measure RAM or CPU ourselves and the docs give no official numbers, so measure on your machine:

Run in terminalbash
docker stats --no-stream hbbs hbbr
  • The real cost is bandwidth through hbbr: roughly 100 KB/s for office work, up to 3 MB/s for HD video-like screens (official docs).
  • Direct connections (when UDP 21116 is open and the NAT allows it) cost the server almost nothing.
  • Setting ALWAYS_USE_RELAY=Y on hbbs forces every session through the relay. Useful for testing, expensive in bandwidth.
  • One user reported high CPU on a Docker server that also had the RustDesk app installed; removing the app from the server fixed it.

FAQ#

Is it really free? Yes. The open-source server is free (AGPL-3.0). Server Pro is a separate, paid product with extra features.

Do I need to set the relay server on the clients? Usually not. RustDesk works it out from the ID server. Set it only if the relay is on another address or port.

How do I stop strangers using my relay? Start both services with -k _, as in this guide, and test with a client that has no key.

Do I need host networking? On Linux it is recommended because logs show real client IPs. On Windows and Mac it does not exist, so map the ports.

Can I use the web client? Yes, but only through a proxy and with ports 21118 and 21119 blocked for everyone else. See the HTTPS section.

Can I use the latest tag? It currently equals 1.1.16. We pin the exact version so a later release cannot surprise you.

Sources#

Checked against the official docs on 2026-10-26. Not run by DIY Lab Hub. Items marked VERIFY are not confirmed in the official docs.