Mit einem selbst gehosteten RustDesk-Server kannst du Rechner auch ohne installierte App fernwarten, denn der Web-Client läuft direkt im Browser. Sobald er hinter einem Reverse Proxy mit HTTPS laufen soll, gibt es aber ein paar Stolperfallen, die in der Doku kaum stehen. Dieser Beitrag zeigt das Setup mit dem Nginx Proxy Manager (NPM) und den typischen Fehlern.
Alle Domains und IPs sind Beispiele. Ersetze example.org und die 172.16.0.x-Adressen durch deine eigenen.
Was am Ende läuft
Domain Ziel Zweck fernwartung.example.orgWeb-Client, Port 5000 Weboberfläche api.fernwartung.example.orgrustdesk-api, Port 21114 API und Admin-Oberfläche ws-id.fernwartung.example.orghbbs, Port 21118 WebSocket zum ID-Server ws-relay.fernwartung.example.orghbbr, Port 21119 WebSocket zum Relay
Der Server (hbbs/hbbr) läuft bei mir als Container unter 172.16.0.49, die API unter 172.16.0.50, der Web-Client unter 172.16.0.51.
Warum vier Domains?
Der Web-Client baut die Verbindung zum ID-Server als wss://<domain>:21116 auf. Auf 21116 lauscht hbbs aber mit seinem rohen TCP-Dienst, ohne TLS und ohne WebSocket. Der Browser bekommt keine Verbindung, und in der Konsole erscheinen Folgefehler wie curConn is undefined. Die eigentlichen WebSocket-Ports sind 21118 (hbbs) und 21119 (hbbr), sie sprechen aber unverschlüsseltes WebSocket, während die Seite über HTTPS läuft. Deshalb terminiert der NPM das TLS und bekommt dafür zwei eigene Subdomains.
Schritt 1: DNS
Lege für alle vier Domains A-Records auf die öffentliche IP des NPM-Hosts an und prüfe sie:
bash
dig +short ws-id.fernwartung.example.org
Erst wenn jede Domain die richtige IP liefert, fordere Zertifikate an, sonst scheitert die Let’s-Encrypt-Challenge.
Schritt 2: Web-Client per Docker Compose
Ich nutze das Image pmietlicki/rustdesk-web-client:v1.
yaml
rustdesk-webclient:
container_name: rustdesk-webclient
image: pmietlicki/rustdesk-web-client:v1
restart: unless-stopped
environment:
- CUSTOM_RENDEZVOUS_SERVER=ws-id.fernwartung.example.org:443
- RELAY_SERVER=ws-relay.fernwartung.example.org:443
- KEY=<OEFFENTLICHER_SCHLUESSEL>
- API_SERVER=https://api.fernwartung.example.org
ports:
- 5000:5000
networks:
docknet:
ipv4_address: 172.16.0.51
Drei Details entscheiden, ob es klappt:
Das Image :v1 lauscht intern auf Port 5000 , nicht auf 80. Mit 5000:80 läuft der Container, aber die Seite bleibt leer.
Die Variablen heißen CUSTOM_RENDEZVOUS_SERVER, RELAY_SERVER, KEY und API_SERVER. RUSTDESK_KEY gehört zu einem anderen Image und wird ignoriert.
KEY ist der öffentliche Schlüssel aus der Datei id_ed25519.pub im Datenverzeichnis von hbbs.
Testen, bevor es an den NPM geht:
bash
docker compose up -d --force-recreate rustdesk-webclient
curl -I http://127.0.0.1:5000/ # erwartet: 200
Schritt 3: Proxy Hosts im NPM
Lege für jede Domain einen Proxy Host an:
Domain Scheme Ziel Port fernwartung.example.orghttp 172.16.0.515000 api.fernwartung.example.orghttp 172.16.0.5021114 ws-id.fernwartung.example.orghttp 172.16.0.4921118 ws-relay.fernwartung.example.orghttp 172.16.0.4921119
Bei allen: Websockets Support an, neues Let’s-Encrypt-Zertifikat, Force SSL und HTTP/2 an. HSTS schaltest du erst nach erfolgreichem Test ein. Bei den beiden WebSocket-Hosts kommt im Zahnrad-Tab noch dies hinein, damit ruhende Sitzungen nicht nach 60 Sekunden getrennt werden:
nginx
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
Schritt 4: Testen
bash
curl -I https://fernwartung.example.org # 200
curl -I http://fernwartung.example.org # 301 auf https
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
https://ws-id.fernwartung.example.org/ # 101 Switching Protocols
Öffne den Web-Client danach in einem privaten Fenster . Er legt seine Einstellungen im localStorage ab und lädt per Service Worker, sonst hängen alte Werte fest.
Schritt 5: Die Ports 21118 und 21119 schließen
Läuft alles über 443, müssen 21118 und 21119 nicht mehr öffentlich erreichbar sein. Die RustDesk-Doku empfiehlt, sie so einzuschränken, dass nur der Reverse Proxy zugreifen kann. Vorher sicherst du die Firewall-Konfiguration:
bash
ufw status numbered > ~/ufw-status-$(date +%F).txt
tar czf ~/ufw-backup-$(date +%F).tar.gz /etc/ufw /etc/default/ufw
Dann entfernst du die Freigaben für die beiden Ports (bei mir Routing-Regeln zu 172.16.0.49) und lässt 21115 bis 21117 sowie 21116/udp bestehen. Zusätzlich löschst du in der Compose des RustDesk-Servers die beiden ports:-Zeilen für 21118 und 21119. Der NPM erreicht die Ports auch ohne Veröffentlichung über das Docker-Netzwerk. Zum Schluss testest du von außerhalb deines Netzes (ein Test vom Server selbst sagt nichts, weil er die Firewall umgeht):
bash
nc -zv -w 5 <öffentliche-IP> 21118 # Timeout erwartet
nc -zv -w 5 <öffentliche-IP> 21117 # Erfolg erwartet
Typische Fehler
301 auf die Hauptdomain oder SSL_ERROR_UNRECOGNIZED_NAME_ALERT: Der NPM, der auf 80/443 antwortet, kennt die Domain nicht. Der Proxy Host fehlt, liegt in einer anderen NPM-Instanz oder ist inaktiv. Auf dem Host hilft docker exec <npm> grep -rl <domain> /config/nginx/proxy_host/.
NPM-Log mit Permission Denied und Cannot read properties of null (reading 'id'): Die Sitzung der Weboberfläche ist ungültig, und der Proxy Host wurde nicht gespeichert. Abmelden, neu anmelden, Host neu anlegen.
Could not resolve host: Der DNS-Eintrag fehlt noch.
HTTP liefert 200 statt eines Redirects: Force SSL ist nicht aktiv.
Offener Punkt
Bei mir lief der Web-Client danach, den Login mit Benutzername und Passwort gegen die API habe ich darin nicht zum Laufen bekommen, obwohl POST /api/login per curl 200 liefert und die Admin-Oberfläche erreichbar ist. Als Kandidaten stehen Mixed Content (API_SERVER mit https:// angeben), CORS und alte Werte im Browser im Raum. Falls du die Ursache findest, lass es mich gern wissen.
Fazit
Der Kern ist, dass der Web-Client nicht mit 21116 zurechtkommt und stattdessen WebSocket über TLS braucht. Mit zwei zusätzlichen Subdomains im NPM läuft das ohne neue öffentliche Ports, und die WebSocket-Ports lassen sich anschließend wieder schließen.