RD‑API‑Server is a modern, self‑hosted API server and admin console for
the RustDesk remote‑desktop client — built in PHP 8.5 / Laravel
with a clean HTML + jQuery + Bootstrap 5 dark dashboard. Runs with one docker compose
command, backed by MariaDB.
⚠️ Not affiliated with RustDesk. This is an independent, third‑party, open‑source project — not an official RustDesk product, nor endorsed by, sponsored by, or connected to the RustDesk team or Purslane Ltd. "RustDesk" is a trademark of its respective owner; the repo is named RD‑API‑Server (shorthand) and references RustDesk only to describe compatibility with its open‑source client. This is a separate implementation of the client's public API, maintained independently.
✅ Stable release: v1.7.0. Connect on any device opens a remote desktop in the browser — screen, sound, mouse, keyboard, clipboard and chat — with no plugin, no download and no client install. Two environment values stand it up on the console's own hostname and certificate, with no reverse-proxy change and no extra public ports, and System → Remote desktop says plainly what is missing if it does not connect. Remote control takes a RustDesk ID directly, and nothing ever connects on its own. Review the release notes (security hardening — read the upgrade notes, the application key now protects more data), and v1.4.0 for what the viewer does and does not do yet.
Implements the RustDesk client API contract and adds the features the client supports that most open‑source API servers don't — including Strategy (Security‑Settings) push and device preset auto‑registration.
Client API (what the RustDesk client talks to)
- Account login with 2FA (TOTP + email codes), OIDC/OAuth (GitHub, Google, generic OIDC — including self-hosted providers on an explicitly trusted private network) and LDAP/AD
- Heartbeat Strategy push — remotely manage client security settings (the Pro
"Settings sync"), with
modified_atchange‑detection and live force‑disconnect - Sysinfo + preset auto‑registration — devices auto‑file into a strategy / device group /
address book on first contact (
--assign→/api/devices/cli, custom‑client presets) - Personal address books (legacy + Flutter granular transports), tags, end‑of‑connection notes
- Audit ingestion (connections incl. RustDesk 1.4.9 auth details, file transfers, security alarms) and session‑recording upload
- Device deployment tokens + approval queue (
/api/devices/deploy) - Group endpoints (
/api/users,/api/peers,/api/device-group/accessible)
Admin console (dark dashboard)
- Dashboard with live stats & charts; brute‑force‑throttled login with optional TOTP 2FA
- Devices — searchable list with bulk‑assign owner / device group / strategy, live‑search pickers
- Strategies editor laid out like the RustDesk client's Settings (General / Security / Network),
~55 catalogued
config_optionswith tri‑state toggles + "set all", plus custom keys and device/user/group assignment - Address Books — RustDesk‑client‑style manager (peer cards, tags, add/edit dialogs) for any user's book, with shared / team books (collaborators at read · read‑write · full‑control)
- Webhooks / notifications — push alarms and connection / device events to Slack, Telegram, or a generic HMAC‑signed JSON endpoint, with per‑hook test + delivery status
- Client Config generator — produce a server‑config string, mobile QR, per‑OS
--configcommand, and renamed‑installer filename to roll out pre‑configured clients - API Keys — scoped keys for the admin REST API (
/api/v1) - Device groups (with a designated default for new devices), Users, User Groups
- Access control (cumulative user‑group + device‑group grants) and Admin Roles
(scoped, delegated console permissions;
is_admin= full access) - Live Sessions + force‑disconnect, Alarms, Recordings, Deploy Tokens, Pending Devices
- OAuth providers, LDAP/AD, SMTP settings; connection / file / login / console‑operation audit logs
- Email subsystem with DB‑managed templates + send logs
Admin REST API (/api/v1) — programmatic access with scoped API keys
(Authorization: Bearer <key> or X-API-Key): devices, users, strategies, audit, address‑book
peers. Ships an OpenAPI 3.1 spec plus Postman and Bruno collections. See
docs/api/README.md.
A quick look at the dark admin console — full set in the screenshot gallery. (Fictional demo data; not affiliated with RustDesk.)
| Devices | Strategy (Security‑Settings) editor |
|---|---|
![]() |
![]() |
| Connection logs — with RustDesk 1.4.9 auth details | Webhooks / notifications |
|---|---|
![]() |
![]() |
The bundled docker-compose.yml runs the published image with
MariaDB/InnoDB, the only supported database. Copy it (or the example below), then create a
local .env file with your DB password and RustDesk endpoints. ADMIN_PASS is optional: omit it
and a strong password is generated at first boot and shown once in the container log.
For a deployment that must remain on the current stable release, set
RUSTDESK_API_IMAGE=ghcr.io/gigaionllc/rustdesk-api-server:1.7.0; latest moves only with a
verified, annotated stable release tag.
# Optional: omit to have one generated and shown once at first boot.
ADMIN_PASS=<unique-admin-password-from-your-password-manager>
DB_PASSWORD=<unique-database-password>
DB_CONNECTION=mariadb
APP_URL=https://api.your-domain.com
TRUSTED_PROXIES=<exact-proxy-address-seen-by-the-application>
SESSION_SECURE_COOKIE=true
RUSTDESK_ID_SERVER=id.your-domain.com:21116
RUSTDESK_RELAY_SERVER=relay.your-domain.com:21117
RUSTDESK_API_SERVER=https://api.your-domain.com
RUSTDESK_PUBLIC_KEY=<contents of id_ed25519.pub> # never id_ed25519The APP_URL, TRUSTED_PROXIES, and SESSION_SECURE_COOKIE values above assume TLS terminates
at a reverse proxy. The exact TRUSTED_PROXIES value is the immediate proxy address observed in
the API container's access log; it is not the public domain or backend address. Bundled Compose
defaults an unset value to * for convenient LAN/proxy deployments, but that trusts forwarded
client IP and scheme values from every immediate caller. Override it with exact IPs/CIDRs whenever
the application port is reachable by anything except the sanitizing proxy. Direct local HTTP
setups can set it explicitly empty and retain request-based cookie detection.
Leave DB_HOST unset for the bundled stack: Compose then uses its internal db service. The
current .env.example comments this setting deliberately. If an existing .env copied from an
older release contains DB_HOST=127.0.0.1, remove it or change it to db before starting the
bundled Compose stack; loopback inside the application container points back to that container,
not MariaDB. DB_HOST and DB_PORT remain the supported overrides for an external MariaDB
endpoint that is reachable from the application container. Those overrides only redirect the
application: the bundled root/development Compose definitions still include a db service and
make the app depend on it. A truly external-only topology therefore needs a reviewed custom
Compose definition/override that removes or replaces both the bundled db service and the app's
depends_on entry.
Then start the stack:
docker compose up -dOpen http://localhost:21114/admin and sign in with ADMIN_USER / ADMIN_PASS. The client
API base is http://localhost:21114/api; in the RustDesk client, set API Server to your
server's URL and log in.
Example docker-compose.yml (copy & edit the CHANGE_ME values)
services:
rustdesk-api:
image: "${RUSTDESK_API_IMAGE:-ghcr.io/gigaionllc/rustdesk-api-server:latest}"
restart: unless-stopped
stop_grace_period: 35s
# Host-based proxy: keep the backend on loopback. For a containerized proxy, remove this
# port and attach both services to a dedicated internal network.
ports: ["127.0.0.1:21114:80"]
environment:
APP_ENV: production
APP_URL: https://api.your-domain.com
SESSION_SECURE_COOKIE: "true"
RUNTIME_SHUTDOWN_GRACE_SECONDS: "30"
# Leave APP_KEY empty for the persistent generated key. Replicas must share explicit keys.
APP_KEY: "${APP_KEY:-}"
APP_PREVIOUS_KEYS: "${APP_PREVIOUS_KEYS:-}"
# Convenience default only; exact proxy IP/CIDR is recommended outside an isolated topology.
TRUSTED_PROXIES: "${TRUSTED_PROXIES-*}"
ADMIN_USER: admin
ADMIN_PASS: "${ADMIN_PASS:-}" # required only while creating the first admin
DB_CONNECTION: mariadb
DB_HOST: db
DB_DATABASE: rustdesk_api
DB_USERNAME: rustdesk
DB_PASSWORD: CHANGE_ME_db # must match the db service below
RUSTDESK_ID_SERVER: id.your-domain.com:21116
RUSTDESK_RELAY_SERVER: relay.your-domain.com:21117
RUSTDESK_API_SERVER: https://api.your-domain.com
RUSTDESK_PUBLIC_KEY: CHANGE_ME_hbbs_public_key # id_ed25519.pub, not id_ed25519
volumes: ["rustdesk-data:/var/www/html/storage"]
depends_on:
db: { condition: service_healthy }
db:
image: mariadb:11.8.9@sha256:79d59758afc91b89b120b0a8904d637f5a3b3e1c4900f29b740d6d46c72fef68
restart: unless-stopped
environment:
MARIADB_DATABASE: rustdesk_api
MARIADB_USER: rustdesk
MARIADB_PASSWORD: CHANGE_ME_db # must match DB_PASSWORD above
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
volumes: ["rustdesk-db:/var/lib/mysql"]
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 12
volumes:
rustdesk-data: {}
rustdesk-db: {}On a new production database, startup stops before seeding if ADMIN_PASS is missing, shorter
than 12 characters, a known/default placeholder, repeated, or derived from ADMIN_USER. Local
and test seeders retain their development-only credential for fixtures. Existing installations
are not rewritten; if an earlier deployment used a default password, reset it in Users or
with php artisan rustdesk:user admin --admin; the command prompts twice without echoing the
password. Automation can pipe one line to --password-stdin.
If TLS terminates at a reverse proxy, preferably set TRUSTED_PROXIES to that proxy's exact IP
address or network CIDR as seen by the application container (comma-separated when there is more
than one). Use the public HTTPS origin for APP_URL and set SESSION_SECURE_COOKIE=true.
An explicit * is supported and is the bundled Compose default when the variable is unset. It
trusts X-Forwarded-For and X-Forwarded-Proto from every immediate caller. This is convenient
for an isolated LAN or proxy-only network, but it is not recommended when a client can reach
the application port directly: that client could spoof the address used by login throttles,
API-key IP allowlists, and audit records. Restrict the port to the proxy or replace * with its
exact observed IP/CIDR. See
Production HTTPS and reverse proxies for safe
network topologies, diagnosis, recovery, and the executable post-deployment check.
Full stack — to run the RustDesk hbbs/hbbr rendezvous + relay alongside the API:
- examples/simple.docker-compose.yml — the whole stack in
one file, no
.env, nothing optional. Change four passwords and two hostnames and it runs. - examples/full-stack.docker-compose.yml — the same stack with pinned image digests, health checks and the hardening notes.
- .env.example — every setting that exists, with what it does. You need none of it to get running; each has a working default.
The console must be HTTPS (browsers only expose the video decoder on a secure page), and hbbs
and hbbr speak plain ws only — so TLS has to be arranged somewhere. There are two ways, and
mixing up their variables is the most common setup mistake:
| Who dials it | What goes in it | |
|---|---|---|
RUSTDESK_WS_ID_UPSTREAMRUSTDESK_WS_RELAY_UPSTREAM |
This container, over the Docker network | rustdesk-hbbs:21118 — a container or service name |
RUSTDESK_WS_ID_URLRUSTDESK_WS_RELAY_URL |
The operator's browser, over the internet | wss://rustdesk.example.com/ws/id — a public URL |
Set the UPSTREAM pair and this server carries the WebSocket on the console's own hostname and certificate: no second certificate, no extra public ports, and nothing to add to your reverse proxy. Relayed video then passes through this container, which is the trade-off.
Set the URL pair instead to keep this server out of the media path, having terminated TLS in front of 21118 and 21119 yourself.
A container name in the URL pair cannot work — the browser is on someone's laptop and cannot resolve it, and the error it produces names nothing useful. System → Remote diagnostics reports that specific mistake, and its browser probe is the only way to confirm the path your operators actually take.
See QUICKSTART.md for all configuration (endpoints, SMTP, retention, metrics, updates) and docs/web-client-deployment.md for proxy configuration.
Breaking database boundary: MariaDB with InnoDB is the only supported database. The runtime and all verification paths reject other drivers. Existing MariaDB deployments that explicitly set
DB_CONNECTION=mysqlmust first back up and confirm every application table is InnoDB, then change it toDB_CONNECTION=mariadb; this renames the Laravel connection only and does not move or rewrite compliant InnoDB data. See the read-only engine audit in the quick start. Existing SQLite deployments must complete a manual migration before upgrading or remain on the last compatible release. The project does not provide an automated converter; follow the SQLite-to-MariaDB migration boundary.DB_URLis also rejected: expand any URL-only MariaDB configuration into the discreteDB_HOST,DB_PORT,DB_DATABASE,DB_USERNAME, andDB_PASSWORDsettings before upgrading, then unsetDB_URL.
PHP 8.5 · Laravel 13 · Blade + jQuery + Bootstrap 5 (no SPA framework) · Eloquent · MariaDB/InnoDB · Nginx + PHP-FPM (runtime image) · Mailpit (dev SMTP) · Playwright (E2E).
Runs entirely in a Docker toolchain image (no host PHP / Composer / Node). The build, test, lint, and runtime‑image workflow lives in docs/DEVELOPMENT.md; architecture and conventions are in AGENT.md.
- CHANGELOG.md — public release history
- v1.7.0 release notes — security hardening: enrollment-only device presets, delegated-admin scope, OIDC sign-in approval, hashed tokens and encrypted peer secrets, login hardening; upgrade notes on the application key
- v1.6.3 release notes — security maintenance: patched dependencies and base images, no behaviour change
- v1.6.2 release notes — a visible pointer, a cancellable connection, and the two WebSocket variable pairs explained
- v1.6.0 release notes — the version in the console, and a redacted support report for issues
- v1.5.0 release notes — searchable pickers everywhere a list can grow, and a scope-picker leak
- v1.4.6 release notes — the shipped examples say which half of the key pair belongs where
- v1.4.5 release notes — the server can no longer hand out a private
key, and
RUSTDESK_PUBLIC_KEY - v1.4.4 release notes — the remote desktop verified end to end, and a check for the wrong half of the server key pair
- v1.4.3 release notes — the published viewer layout that stopped it running at all
- v1.4.2 release notes — the Remote control screen, and why nothing connects on its own
- v1.4.1 release notes — standing the remote desktop up without a reverse-proxy change, and the diagnostics page
- v1.4.0 release notes — the browser remote desktop, what it does not do yet, and the reverse-proxy configuration it needs
- v1.3.0 release notes — SSO group to role mapping, break-glass
administrator, optional
ADMIN_PASS, release candidates - v1.2.0 release notes — private-network OIDC providers, what the opt-in never permits, and OIDC failure diagnostics
- v1.1.0 release notes — Nginx/PHP-FPM runtime, proxy defaults, capacity guidance, and rollback
- v1.0.1 release notes — dark-default patch and faster native image publishing
- v1.0.0 release notes — first stable release and upgrade boundaries
- QUICKSTART.md — deployment & configuration
- docs/DEVELOPMENT.md — build, test, lint, contribute
- docs/sqlite-to-mariadb.md — breaking upgrade boundary for installations created with the retired SQLite path
- docker/README.md — pinned container inputs and their update process
- AGENT.md — the project's source‑of‑truth guide (architecture, conventions,
task lookup);
CLAUDE.mdpoints here - Wiki/ — architecture knowledge base (design system, core docs)
- docs/modernization/ — the research → plan → status that drove this build, including the client API contract
Built to the RustDesk client API contract. Pairs with a RustDesk rendezvous/relay
(hbbs/hbbr) — point clients at this server's API and your hbbs/hbbr for signaling and
relay. Set RUSTDESK_ID_SERVER, RUSTDESK_RELAY_SERVER, and RUSTDESK_PUBLIC_KEY (see QUICKSTART).
GNU Affero General Public License v3.0 or later (AGPL‑3.0‑or‑later). See LICENSE. © 2026 Gigaion LLC.
The AGPL is a strong copyleft license: if you modify this software and make it available to others — including over a network (e.g. running it as a hosted service) — you must offer those users the complete corresponding source code of your modified version under the same license. In short: improvements stay open.
This is an independent, community/third‑party project and is not affiliated with, endorsed by, or sponsored by RustDesk or Purslane Ltd. "RustDesk" and any related marks are the property of their respective owners and are referenced here solely to indicate compatibility with the open‑source RustDesk client's public API. All code in this repository is an original, separate implementation and is distributed under the AGPL‑3.0‑or‑later license above.
RD‑API‑Server is developed, modernized, and maintained with extensive use of AI coding agents. The PHP 8.5 / Laravel rewrite, the wire‑compatible client API, the Strategy/Settings‑push and preset auto‑registration features, the security hardening, the test suites (PHPUnit + Playwright), and the documentation were all produced and verified with AI assistance. Agent and contributor guidance follows the AGENT.md standard.
This project drew inspiration from the RustDesk client and ecosystem and from existing open‑source RustDesk API panels (lejianwen/rustdesk-api and lantongxue/rustdesk-api-server-pro) — thanks for the ideas and groundwork.




