APP_URL 必须填客户端和浏览器实际可达的地址,留 localhost 时走中继的会话会卡住;首次启动默认账号为 admin/changeme,需尽快修改或用 CORTENDESK_ADMIN_USER/CORTENDESK_ADMIN_PASSWORD 覆盖;21118/21119 不要对外发布,WebSocket 由容器内 nginx 在回环上桥接;客户端需手动填入 /data/rustdesk/id_ed25519.pub 中的公钥;上传客户端安装包时,前置反向代理的请求体上限也要同步调大。
cortendesk
marcpopeAGPL-3.0活跃维护基于 Web 的开源管理控制台,用来集中管理 RustDesk 自建服务器和客户端,省去命令行配置的麻烦。
A web based, open-source console for managing the RustDesk OSS Server and Client.
docker run -d --name cortendesk -e APP_URL=https://rd.example.com -p 8080:8080 -p 21115-21117:21115-21117 -p 21116:21116/udp -v cortendesk-data:/data ghcr.io/marcpope/cortendesk:1.23.0README 未说明 CPU/内存/显存要求;仅需能运行 Docker 的 Linux 主机,控制台与内嵌 hbbs/hbbr 均在容器内运行,无需 GPU。
单容器自托管 RustDesk 服务端加管理控制台,带浏览器远程桌面客户端
基于 Web 的开源管理控制台,用来集中管理 RustDesk 自建服务器和客户端,省去命令行配置的麻烦。
核心能力
- 单镜像打包 hbbs、hbbr 与 Laravel 控制台
- 浏览器内直接查看、控制设备并传文件
- 设备分组、预注册、回收站与 CSV 导出
- 用户与设备组权限隔离,客户端 API 同样受限
- 审计日志记录连接方式、文件传输与安全告警
容易踩的坑
- v2.0.0-beta.1CortenDesk 2.0.0-beta.1pre-release2026-11-08 · 2026-11-08 18:20 UTC
查看更新说明
Very early, for testing only. Not recommended for production. The stable release is 1.23.0.
Monitoring and the agent are completely optional. Everything else works exactly as before without them. Monitoring stays off until you turn it on, and nothing is installed on any machine unless you install the agent there.
New
- Monitoring, optional and off by default (Settings > Monitoring). Install the CortenDesk Agent on Windows, macOS and Linux from Monitoring > Add agent. It reports health, inventory, antivirus, firewall, disk encryption, pending updates and SMART.
- A fleet dashboard showing what needs attention, with Acknowledge for issues you accept.
- Remote commands, off until you allow them, with their own permission: upgrade RustDesk to an official release, install OS updates, reboot. One machine or many.
- Agents update themselves from signed releases and roll back if a new version can't reach the console.
Notes
- Back up first. Going back to 1.23.0 keeps your data, because the beta only adds tables and columns.
- Agent: CortenDesk Agent 0.2.0. Windows builds are signed (publisher Marcus Pope), macOS builds are notarized.
- Agents use plain http only when the console is on a private network.
betafollows the newest beta;lateststays on the stable release.
docker pull marcpope/cortendesk:2.0.0-beta.1 docker pull ghcr.io/marcpope/cortendesk:2.0.0-beta.1 - v1.23.0CortenDesk 1.23.0最新正式版2026-11-07 · 2026-11-07 12:49 UTC
New
- Web terminal on xterm.js. The remote terminal is a real terminal now: colours, cursor movement, full-screen programs like
topandvim, resizing, and clickable links. It loads only when you open a terminal. (#158, contributed by @theores) - Device page tabs. A device's page opens on an Overview: a hardware summary (CPU, memory, OS build, RustDesk version), a map of the devices it connected to and from with session counts, and the latest file transfer, alarm and notification. Connections, Activity and Details have their own tabs.
- Default password for a shared address book. Edit a shared book to set one password for every entry that has none of its own. RustDesk clients signed in to the console use it to connect. (#162, requested by @DezValT1r)
- Duplicate a strategy. Strategies has a Duplicate action that copies a strategy's settings into a new one. (#159, requested by @kirillv-artur)
- Skip the SMTP certificate check. Settings > Email has an option for mail servers with a self-signed certificate or one that doesn't match the host name. (#160, requested by @burrsirr)
- About dialog. Click the version in the sidebar to see the version, its release date, whether an update is out, and the release notes.
- Overview. "How Sessions Connected" is a bar list next to the platforms chart, which leaves room for the rows below.
Fixes
- Cloned machines no longer show as duplicates. Two devices that share a hardware ID but both report in more than an hour after the second appeared are separate machines, not a reinstall. (#159, @kirillv-artur)
- Device list: row actions are icons on desktop, and on phones the checkbox is on the left of each card. (#161, @soundsessential)
- Web client: the Type box fits inside its popover. (#164, @soundsessential)
- Web client: when the browser's hardware video decoder fails, the client retries the codec in software before turning it off. (#139, thanks @andydld)
- Sidebar: tighter menu spacing, cosmetic changes and now just 1 place where it says there's an update.
Notes
- One migration: adds
default_password_enctoaddress_books. Checked on MySQL 8 and MariaDB 10.6. - Bundles CortenDesk Server 1.5.1, unchanged.
docker pull marcpope/cortendesk:1.23.0 docker pull ghcr.io/marcpope/cortendesk:1.23.0Feeling generous? Sponsor this project: https://github.com/sponsors/marcpope/
- Web terminal on xterm.js. The remote terminal is a real terminal now: colours, cursor movement, full-screen programs like
- v1.22.0CortenDesk 1.22.02026-11-05 · 2026-11-05 21:08 UTC
查看更新说明
New
- Email footer. Settings > Email has a field for the line at the bottom of every email. Leave it blank to keep "Sent by CortenDesk at" and the console address. (#153, requested by @EBMCreative)
- Strategy column and IP sort. The device list has a Strategy column (admins only, under Columns) showing the strategy in force. You can sort by it. IP sorts by address now, so 10.0.0.9 comes before 10.0.0.10 and IPv4 before IPv6. (#154, requested by @kirillv-artur)
- Aliases in Active sessions. The Overview shows the device's alias, or its hostname if there's no alias, and the controlling side's alias when it's one of your devices. (#154)
- Inherited strategy by name. In Edit device, the Inherit option names the strategy the device would get, e.g. "Inherit (owner, group, or default): Office laptops". (#154)
Fixes
- Web client: uploads over WebRTC no longer stop partway. They now wait for the data channel to drain. Before, a fast upload queued more than Chrome allows (16 MB) and the channel closed. (#155, @TheoGal)
- Web client: double-clicking a folder in the file manager opens it again. (#157, @DezValT1r)
- The Share screen card sits under Address books on the device page, instead of below the notification list. (#156, @soundsessential)
- Device and user pages: even card spacing, accent-coloured header links, correct arrows on outgoing file transfers, and known device aliases on incoming rows. (#149, @soundsessential)
- Web client: with
?debug=1, the console log shows video decoder events (codec chosen, first picture, waits for a key frame, stalls, errors). This helps with black-screen reports. (#139)
Notes
- Bundles CortenDesk Server 1.5.1, which drops relay requests with a malformed relay uuid. See its release notes.
- No migrations.
docker pull marcpope/cortendesk:1.22.0 docker pull ghcr.io/marcpope/cortendesk:1.22.0Feeling generous? Sponsor this project: https://github.com/sponsors/marcpope/
- v1.21.1CortenDesk 1.21.12026-11-05 · 2026-11-05 03:03 UTC
查看更新说明
Security
- Symfony 8.1.11. Fixes CVE-2026-118514 (HEAD requests bypassing method-limited access rules), CVE-2026-118516 (host placeholders matching the start of the path in compiled URL matchers), and four advisories without a CVE, about forwarded headers, trusted proxy ranges and generated URLs. Updating is recommended.
Notes
- No other changes from 1.21.0. No migrations. Bundles CortenDesk Server 1.5.0, as before.
docker pull marcpope/cortendesk:1.21.1 docker pull ghcr.io/marcpope/cortendesk:1.21.1Feeling generous? Sponsor this project: https://github.com/sponsors/marcpope/
- v1.21.0CortenDesk 1.21.02026-11-05 · 2026-11-05 02:47 UTC
查看更新说明
New
- Share links. Send someone a secret link to one device. It opens the web client in their browser, with no account and no RustDesk install, and the person at the device accepts the connection in RustDesk's accept window. You choose how long the link works (15 minutes to 7 days) and how many people may connect at once. Creating, opening and ending links goes in the audit log. In approved-only mode the link only reaches that one device (needs the bundled Server 1.5.0).
CORTENDESK_SHARE_LINKS=falseturns it off. See Share links. (#142, by @frankhommers) - The web client on phones and tablets. It has one bar of controls docked by orientation, the phone's own keyboard (any script and emoji), extra keys (Esc, Tab, modifiers, arrows, F-keys, shortcuts), touch gestures, pinch zoom and a Trackpad mode. It reconnects by itself when the connection drops, and Add to Home Screen gives a full-screen app on iPhone. (#136 and #148, by @alee-ibrahim)
- Add-only Automation API tokens. A token can be limited to adding entries to chosen shared address books and nothing else. It gets back only the new entry's ID, and an ID that's already in the book gets a 409. Safe to ship in a rollout script. (#151, requested by @ad2012)
- Peer passwords in the Automation API. Creating or updating an address book entry takes a write-only
password, stored encrypted and never returned. Peers showhas_passwordinstead. (#151) - Outgoing sessions on the device page. Recent connections and file transfers also list the sessions a device started, with the device it reached. (#149, requested by @soundsessential)
- Slovak. The console, emails and web client in 12 languages. (#141, by @Vaso73)
Fixes
- Rejected devices are listed in the recycle bin, so their IDs can be freed. Adding a device with a taken ID says where that ID is. (#140, by @adiwirya)
- Web client: a video decoder that stalls without an error is replaced, instead of being sent another key frame. This targets the black screen that re-selecting the display used to cure. (#139)
- Address book tags created through the Automation API no longer fail with a 500 on MySQL and MariaDB.
- Session types use the Overview chart's colours in the logs, and File and Port Fwd are translated. (#143, @soundsessential)
- The file transfer log shows full paths, and IP addresses no longer wrap. (#144, #146, @soundsessential)
- iOS devices show an Outgoing only badge, and a dash instead of "?" and "never". (#147, @soundsessential)
GET /api/peersincludes the device's console alias, so the RustDesk client's group tab shows it. (#28, @soundsessential)- The Overview connections chart no longer shows "Infinity" on its axis when there are no sessions.
Notes
- Bundles CortenDesk Server 1.5.0, which adds the device-bound tickets that share links need. See its release notes. With an older external
hbbs, share links work in open mode only. - Three migrations: the share link tables, an index on the connection and file transfer logs, and a column on API tokens. They run on start, and were tested on MariaDB 10.6 and MySQL 8.
- MySQL 9.7 works: the full test suite gives the same results as on 8.4. (#150, @Tom14x)
docker pull marcpope/cortendesk:1.21.0 docker pull ghcr.io/marcpope/cortendesk:1.21.0Feeling generous? Sponsor this project: https://github.com/sponsors/marcpope/
- Share links. Send someone a secret link to one device. It opens the web client in their browser, with no account and no RustDesk install, and the person at the device accepts the connection in RustDesk's accept window. You choose how long the link works (15 minutes to 7 days) and how many people may connect at once. Creating, opening and ending links goes in the audit log. In approved-only mode the link only reaches that one device (needs the bundled Server 1.5.0).
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
CortenDesk
A complete, self-hosted drop-in replacement for the open-source RustDesk server — ID server, relay and professional console in one image, with a fully native in-browser remote desktop client.
CortenDesk is everything a self-hosted RustDesk deployment needs, in a single container: the ID/rendezvous server (hbbs), the relay (hbbr), and a clean, professional console — device fleet management, users and scoped access, address books, audit logs, and a web client that can view, control, and transfer files to your devices straight from the browser. No installer, no Electron, no paid tier. Stock RustDesk clients connect to it unchanged.
It replaces the open-source server, and fixes what that server gets wrong. The bundled hbbs/hbbr are CortenDesk Server, our AGPL fork of rustdesk-server. The headline fix: the open-source hbbs never completes the signalling key exchange that RustDesk clients 1.4.1 and newer start whenever they are signed in to a console, so every connection from a signed-in client fails with Failed to secure tcp: deadline has elapsed — which breaks the address book, the main reason to sign in at all. Upstream treats that as out of scope. We implemented the missing half.
Note: It is no longer recommended to use the official hbbs/hbbr server from Rust Desk due to bugs not fixed. Use the bundled one instead.
Built on Laravel + Livewire with precompiled assets: there is no frontend build step. Clone, configure, migrate, serve.
Features
Console
- Devices — live fleet with presence, platform icons, aliases, device groups ("folders"), pre-registration, and a recycle bin. One-click connect via
rustdesk://deep links (the scheme is a setting, for clients built with rdgen) or the built-in web client. Pick your own table columns (CPU, memory, IP, UUID and more — saved per user), export the current view to CSV, and multi-select rows to bulk-delete or add devices to an address book. Each device has a detail page with a hardware summary, a map of who it connected to and from, its file transfers, alarms, notifications and address books, and a button that frees its ID on the bundled ID server so a reinstalled machine can take it back. - Server access control — with the embedded ID server (or CortenDesk Server 1.1.0+), limit the server to devices you approved, and mark devices incoming only so they can be controlled but cannot start sessions. New devices can land in a default group, and a pending device can be approved automatically when its user signs in. The ID server enforces both. It also reports each device's LAN address seen during connection setup, shown as an optional LAN IP column. See CortenDesk Server.
- Users & access scoping — a detail page per user shows their devices, address books, sign-ins, console actions and connections. Admins see everything; regular users see only their own devices plus device groups granted to them or their user groups. The RustDesk client API is scoped with the same rules.
- Address books — full support for the modern multi-address-book API and the legacy API: shared books, share rules (everyone / user / group), tags with colors, and a default password per shared book.
- Audit logs — connections (with the note RustDesk clients can leave at the end of a session, and how each one got through: LAN, direct, WebRTC or relay), file transfers, console logins, and security alarms (brute-force/blocked-access events); filterable, exportable to CSV, with configurable retention and automatic nightly pruning. Optionally every record also goes to the container log as one JSON line, for a log shipper.
- Single sign-on (OIDC) — sign in with Keycloak, Authentik, Entra ID, Okta, Google Workspace or any OpenID Connect provider. Authorization-code flow with PKCE, verified ID tokens, just-in-time account creation with optional approval, an email-domain allowlist, and optional provider sign-out. Password sign-in can be switched off, on the console and in the RustDesk client — and returns by itself if SSO is disabled or left incompletely configured. For a provider that is unreachable while still configured,
CORTENDESK_OIDC_DISABLED=trueforces it off and brings the password form back. - Device policies (strategies) — push client settings to devices from the console: permissions, security and password rules, capture options, and the STUN/TURN servers RustDesk 1.5 uses for WebRTC. Assign to a device, a user or a device group, with the most specific assignment winning. Optionally enforced, so a local change is reverted on the next heartbeat. Duplicate a strategy to start a new one from it. The ID whitelist can take whole device groups, and follows their membership as devices come and go.
- Two-factor authentication — TOTP with single-use recovery codes, optionally required for everyone or for administrators only, with an administrator reset and a break-glass command. The RustDesk client asks for the same code when it signs in with a password.
- Delegated administration — roles with a permission matrix over each console area, so you can grant someone the users screen without handing them the whole console.
- Automation API — scoped bearer tokens and a REST API for users, devices, groups, address books and audit logs, plus support for the RustDesk client's
--assignflag for unattended deployment. A token can be limited to adding entries to chosen address books, so a rollout script can carry it safely. - Email — SMTP settings with a test send (and a certificate-check skip for self-signed servers), user invitations by email, self-service password reset, and an optional emailed code when signing in from a new browser.
- Backups — one archive with the whole database, every setting, the app key and the bundled server's key pair and peer database. Runs daily, keeps 7 daily, 8 weekly and 6 monthly, mirrors them to FTP/FTPS or S3-compatible storage, and restores from the list or an upload, re-keying encrypted values for a fresh install. Every run goes in the console log, and notifications can report each success or failure.
- Dashboard — live stat tiles, active sessions, 14-day connection charts, platform and version breakdowns.
- Importer — one artisan command migrates everything (users with passwords intact, devices, address books, audit history) from a
lejianwen/rustdesk-apidatabase. - Mobile-first — every screen works on a phone; wide tables degrade to card lists. Dark and light themes.
- 12 languages — English, Deutsch, Español, Français, Italiano, Magyar, Nederlands, Polski, Português (Brasil), Русский, Slovenčina and 简体中文, including emails and the web client. Each user picks their own; without a choice the console follows the browser.
Client API
- Implements the RustDesk client HTTP API: login/tokens, heartbeat and sysinfo presence, address books, group tab, audit ingestion. Point stock RustDesk clients at CortenDesk as their API Server — no client patches needed.
Share links
- Send someone a secret link to one device. It opens the web client in their browser: no account, no RustDesk. The person at the device accepts the connection in RustDesk's own accept window. You choose how many people may connect at once and how long the link works (15 minutes to 7 days); ending a link disconnects open share pages within half a minute. See Share links.
Native web client
- A from-scratch TypeScript implementation of the RustDesk wire protocol (rendezvous → relay → NaCl handshake → login), running entirely in the browser over WebSocket relays. Not a WASM port — readable, auditable source.
- WebRTC — to devices running RustDesk 1.5 or later, the web client also offers a WebRTC connection and goes straight to the device when it can, with the relay as fallback. A TURN server of your own (coturn works) gets it through mobile and carrier NAT.
- Hardware-accelerated video via WebCodecs (VP8/VP9/H.264/H.265/AV1 as supported), remote audio, two-way voice calls, text chat, clipboard both ways, multi-monitor switching, Ctrl+Alt+Del, session stats. The keyboard sends physical keys to Windows and Linux hosts like the desktop client, so AltGr, dead keys and punctuation work on non-US layouts.
- File transfer — an in-session dual-pane manager: browse the remote filesystem, send/receive files and folders with progress, resume-aware digests, conflict prompts, and drag-and-drop. Uses the File System Access API on Chromium; falls back to picker/Downloads elsewhere.
- Remote terminal — a full xterm.js terminal on Windows, macOS and Linux devices (RustDesk 1.4.1+), from inside a desktop session or straight from the device list with its own sign-in.
- Phones and tablets — one bar of controls docked by orientation, the phone's own keyboard (any script and emoji), an extra-keys bar with Esc, Tab, modifiers, arrows and F-keys, touch gestures, pinch zoom and a Trackpad mode, automatic reconnect when the connection drops, and Add to Home Screen for a full-screen app on iPhone.
- Devices that ask for a 2FA code get one prompted for, in desktop and terminal sessions alike.
- Saved passwords (hashed, never plaintext) with auto-login per device.
- Best experienced in Chrome/Edge; the desktop stream requires WebCodecs. Voice calls need Chrome or Edge on HTTPS (or
http://localhost) plus microphone permission. - HTTPS recommended, not required. This is about video quality, not whether it works: over HTTPS the client uses WebCodecs for hardware-accelerated VP8/VP9/H.264/H.265/AV1, and over plain
http://it falls back to H.264 through Media Source Extensions, which is not restricted to secure contexts. The fallback is automatic and needs no configuration; it is limited to H.264 and reports no per-frame statistics.http://localhostcounts as secure. Signalling followsAPP_URLeither way — set it to the address browsers actually use, or overrideCORTENDESK_WS_ID_URL/CORTENDESK_WS_RELAY_URLwhen your WebSocket endpoints live somewhere else.
Requirements
- PHP 8.3+ with Composer (8.5 recommended; use 8.5.12, 8.4.27, 8.3.36 or later, which fix CVE-2026-109618 and CVE-2026-109619)
- MySQL/MariaDB (SQLite works for evaluation)
- nginx + php-fpm (or any Laravel-capable web server)
- A RustDesk server (
hbbs/hbbr) — included in the Docker image; only needed separately for a manual install - For the web client: a proxy bridging WebSockets to hbbs/hbbr ports 21118/21119 —
wss://over HTTPS, orws://if you serve the console over plain HTTP — sample config below. The Docker image already does this internally.
Quick start with Docker
One image, one command. It brings up the console, the ID server and the relay, generates the server key pair on first boot, and wires all three together:
docker run -d --name cortendesk \
-e APP_URL=https://rd.example.com \
-p 8080:8080 -p 21115-21117:21115-21117 -p 21116:21116/udp \
-v cortendesk-data:/data \
ghcr.io/marcpope/cortendesk:1.23.0
APP_URL is the only setting that matters: it is the address your clients and
browsers reach, and the ID server, the relay address handed to clients, and the
web client's WebSocket URLs are all derived from it. Leave it as localhost and
sessions that need the relay will hang.
First boot creates admin / changeme (override with CORTENDESK_ADMIN_USER
/ CORTENDESK_ADMIN_PASSWORD) and uses SQLite in the /data volume — see
docker-compose.yml for a MySQL setup. Read the generated public key off the
Settings screen, or from /data/rustdesk/id_ed25519.pub; it is what you put in
each client's Key field.
Ports: 8080 console and client API; 21115 NAT test; 21116 signalling, TCP and
UDP; 21117 relay; 21118/21119 the WebSocket pair. Leave those two
unpublished: nginx here already bridges /ws/id and /ws/relay over loopback.
Put a TLS reverse proxy in front of 8080 and the web client works with no
further configuration.
Desktop clients over port 443. RustDesk 1.4.9 and newer can run entirely
over WebSocket, for networks that block the 2111x ports. Set the client's ID
server to the console's hostname (no port), its API server to
https://<that host>, and turn on allow-websocket (Use WebSocket, in the
client's network settings; strategies can't push it). It then connects to wss://<host>/ws/id and /ws/relay through
the same proxy as the web client. Those sessions always go through the relay.
Bringing your own server. Point CortenDesk at hbbs/hbbr you already run:
-e CORTENDESK_EMBEDDED_SERVER=false \
-e CORTENDESK_ID_SERVER=hbbs.example.com:21116 \
-e CORTENDESK_RELAY_SERVER=hbbs.example.com:21117 \
-e CORTENDESK_PUBLIC_KEY="<contents of id_ed25519.pub>"
To enforce approved-only access and incoming-only devices there, run
CortenDesk Server 1.1.0 or later and give both sides the same secret:
CORTENDESK_SERVER_SECRET on the console, and CORTENDESK_SERVER_SECRET plus
CORTENDESK_CONSOLE_URL on hbbs. The embedded server needs none of this.
See CortenDesk Server.
Client installer uploads. System → Client Downloads accepts installers up
to 512 MB. Change that with -e CORTENDESK_DOWNLOADS_MAX_MB=1024; the container
applies it to nginx and PHP at boot, so nothing else needs editing. A reverse
proxy in front of the container has its own body limit (nginx defaults to 1 MB)
and must allow the same size.
Other container settings.
| Variable | Default | What it does |
|---|---|---|
CORTENDESK_BACKUPS_PATH | /data/backups | Where Settings → Backups writes its archives. Keep it in the volume. |
CORTENDESK_CLIENT_URL_SCHEME | rustdesk | Default scheme of the "open in RustDesk" links. A client built with rdgen registers its own app name instead. Settings → Server overrides it. |
CORTENDESK_RUSTDESK_DIR | /data/rustdesk when embedded | The ID server's data directory, so backups carry the key pair and peer database. Empty with external servers. |
CORTENDESK_HTTP_PORT | 8080 | Port nginx listens on inside the container. The healthcheck and the embedded hbbs follow it. Publish it with -p <host>:<port>. |
CORTENDESK_UPDATE_CHECK | true | false stops the request to GitHub every 6 hours and hides the "update available" badge and status line. |
CORTENDESK_LOG_LEVEL | unset | One level for everything: debug, info, notice, warning, error or critical. Sets Laravel's LOG_LEVEL and hbbs/hbbr's RUST_LOG; warning and above also turn off php-fpm's line per request. LOG_LEVEL or RUST_LOG set on their own still win. |
CORTENDESK_AUDIT_LOG | false | true also writes every audit record as one JSON line to the container log, for a log shipper. Lines start with {"cortendesk_audit":1,. |
CORTENDESK_AUDIT_LOG_STREAM | php://stderr | Where those lines go. A file path writes there instead. |
CORTENDESK_PRIVATE_KEY_FILE | unset | A file holding the server's private key (the content of id_ed25519), e.g. a Docker secret at /run/secrets/rustdesk_key. Installed as the bundled server's key on every start; the public key is derived from it. |
CORTENDESK_WS_TRUSTED_PROXIES | 127.0.0.1/32 | Proxies whose X-Forwarded-For the embedded hbbs and hbbr believe for a WebSocket client (web client, or a desktop client set to WebSocket). The default is the container's own nginx. If your proxy sends /ws/id and /ws/relay straight to published ports 21118/21119, add its address as the container sees it, e.g. 127.0.0.1/32,172.16.0.0/12. Comma-separated CIDRs. |
UDP_PUNCH, IPV6_PUNCH | Y | UDP hole punching and IPv6 peer-to-peer for RustDesk 1.5 clients that turn on enable-udp-punch or enable-ipv6-punch: those sessions can connect directly instead of through the relay. UDP 21116 must be published. N turns either off. |
CORTENDESK_WEBCLIENT_AB_PASSWORDS | true | The web client signs in to a device with a password from an address book the user can read, as the desktop client does. false always asks. |
CORTENDESK_FPM_MAX_CHILDREN | 24 | PHP workers. See sizing below. |
CORTENDESK_NGINX_WORKER_CONNECTIONS | 4096 | Connections per nginx worker. See sizing below. |
CORTENDESK_NOTIFY_TIMEOUT | 15 | Seconds (1 to 60) a scheduled notification, or a retry of one, waits for Apprise. Sends made while handling a request wait 3. |
RUST_LOG takes the full filter syntax if you need it, e.g.
RUST_LOG=info,hbbs=debug,hbbr=debug. A plain RUST_LOG=debug also logs the
HTTP client hbbs uses to poll the console.
Sizing for larger fleets. Every heartbeat, inventory upload and console
page takes a PHP worker while it runs. The image starts up to 24 workers, or
fewer when the container has a memory limit (about one per 80 MB of it, at
least 5). A worker uses about 35 MB, most of it shared, so 24 need well under
1 GB. If the log shows server reached pm.max_children setting, raise
CORTENDESK_FPM_MAX_CHILDREN; with MySQL, keep it below the database's
max_connections. SQLite commits one write at a time, so for several hundred
devices or more use MySQL or MariaDB (see docker-compose.yml).
nginx runs one worker per CPU with 4096 connections each. Each proxied request
holds two file descriptors, and the container may open only as many as its
limit allows. Many Docker hosts start containers with a soft limit of 1024;
the entrypoint raises it to the hard limit (up to 65536) and logs a warning
when that is still under 4096. If it does, start the container with
--ulimit nofile=65536:65536 (Compose: ulimits: { nofile: 65536 }).
A reverse proxy in front of the container has its own connection, file descriptor and timeout limits. Raise those too, or it becomes the bottleneck.
Coming from separate hbbs/hbbr containers. Stop them, then mount their data
directory at /data/rustdesk. The key pair and peer database are adopted as
they are, so every device keeps its ID and needs no reconfiguring:
-v /path/to/your/rustdesk/data:/data/rustdesk
Manual installation
There is no installer — setup is a standard Laravel deployment:
git clone https://github.com/marcpope/cortendesk.git
cd cortendesk
composer install --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate
Edit .env — the CortenDesk-specific settings:
APP_NAME=CortenDesk
APP_URL=https://console.example.com
DB_CONNECTION=mysql
DB_DATABASE=cortendesk
DB_USERNAME=cortendesk
DB_PASSWORD=********
# Your RustDesk server
CORTENDESK_ID_SERVER=hbbs.example.com:21116
CORTENDESK_RELAY_SERVER=hbbs.example.com:21117
CORTENDESK_PUBLIC_KEY=<contents of your id_ed25519.pub>
# Shared with hbbs (CortenDesk Server 1.1.0+) for device access control,
# see the CortenDesk Server README. Any long random string.
CORTENDESK_SERVER_SECRET=
# Native web client (wss endpoints your proxy exposes, see below)
CORTENDESK_NATIVE_WEBCLIENT=true
CORTENDESK_WS_ID_URL=wss://console.example.com/ws/id
CORTENDESK_WS_RELAY_URL=wss://console.example.com/ws/relay
# false: no update check against GitHub, no update badge
CORTENDESK_UPDATE_CHECK=true
Then migrate and cache:
php artisan migrate --seed
php artisan config:cache route:cache view:cache
Serve public/ with nginx + php-fpm as usual for Laravel. Log in as admin / changeme and change the password immediately.
Client Downloads accepts installers up to CORTENDESK_DOWNLOADS_MAX_MB (default
512). nginx and PHP reject anything larger than their own limits before the
console sees it, so raise them to at least that size plus a few MB:
client_max_body_size 528m;
fastcgi_read_timeout 300s; # in the PHP location: copying and hashing a big file takes a while
; php.ini, or a file in PHP's conf.d; restart php-fpm afterwards
upload_max_filesize = 528M
post_max_size = 528M
Add the Laravel scheduler to cron (log retention and other maintenance run through it; the Docker image does this automatically):
* * * * * cd /path/to/cortendesk && php artisan schedule:run >> /dev/null 2>&1
Behind a reverse proxy (TLS termination)
CortenDesk honors X-Forwarded-* headers, so it works out of the box behind a
TLS-terminating proxy (Traefik, Caddy, nginx-proxy-manager, Cloudflare, …) that
forwards to the container/app over plain HTTP. Make sure your proxy passes
X-Forwarded-Proto (all of the above do by default) and set APP_URL to your
public https URL. The session cookie carries the Secure flag on every HTTPS
request, so the same console still accepts sign-in over plain HTTP on the LAN.
Set SESSION_SECURE_COOKIE=true only to refuse HTTP sign-in outright. No
mixed-content issues — assets are generated with the correct scheme from the
forwarded headers.
Forwarded headers are trusted only from private/loopback addresses (Docker
networks, a same-host proxy) so that clients reaching the app directly cannot
forge their IP in the audit logs. If your proxy connects from a public
address, list it explicitly: TRUSTED_PROXIES=203.0.113.7 (comma-separated,
CIDRs allowed).
Getting this wrong is worth more than a wrong column in a log: every request
then appears to come from the proxy, so devices all record the same
last_online_ip and the per-address sign-in limiter treats every user as
one address, which can lock real users out.
WebSocket bridge for the web client
Browsers can't open raw TCP to hbbs/hbbr, so the web client speaks WebSocket.
Running the Docker image? You do not need the block below. The container
already bridges /ws/id and /ws/relay to hbbs/hbbr itself — point your proxy
at the container on 8080 for all paths and make sure it forwards WebSocket
upgrade headers. The snippet below is for a manual/VM install, where hbbs
and hbbr are reachable on the host. Full examples for Caddy, Traefik and nginx:
Reverse proxy and TLS.
For a manual install, add to your TLS server block (adjust the upstream host if hbbs runs elsewhere):
location = /ws/id {
proxy_pass http://127.0.0.1:21118/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
location = /ws/relay {
proxy_pass http://127.0.0.1:21119/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
Pointing RustDesk clients at CortenDesk
In each RustDesk client (or via a mass-deployed config): Settings → Network — set ID Server, Relay Server, Key, and API Server = your console URL. Devices then appear in the console within a heartbeat (~15 s). The Settings screen shows copy-paste values for all four fields.
Migrating from lejianwen/rustdesk-api
php artisan cortendesk:import-lejianwen /path/to/rustdeskapi.db --dry-run # preview
php artisan cortendesk:import-lejianwen /path/to/rustdeskapi.db # import
Users (original bcrypt passwords), devices (deduplicated), address books, share rules, and audit history come across. Go-encrypted address-book entry passwords cannot be decrypted and must be re-saved by users.
Rebuilding the web client (optional)
The browser client ships prebuilt in public/rdclient/. To hack on it:
cd webclient
npm install
npm run build # or: npm run typecheck
License
CortenDesk is licensed under the AGPL-3.0-only (see LICENSE).
Third-party front-end files under public/assets/ (Bootstrap, Remix Icon, ApexCharts, the Figtree font) keep their own permissive licenses; see NOTICE. The vendored RustDesk protocol definitions (webclient/protos/) are AGPL, consistent with this repository.
CortenDesk is an independent project and is not affiliated with or endorsed by RustDesk / Purslane Ltd.