Repository navigation
Install on a VM
A standard Laravel deployment: nginx + php-fpm + MySQL. There is no installer and no frontend build step — the assets are precompiled and committed.
Examples below assume Ubuntu 22.04/24.04 and /opt/cortendesk. Adjust paths and
package names for your distribution.
-
PHP 8.3 or newer (8.4 recommended) with
pdo_mysql,mbstring,intl,bcmath,xml,curl,zip,gd,opcache - Composer
- MySQL 8 or MariaDB 10.6+
- nginx (or any Laravel-capable web server)
- A running open-source RustDesk server (
hbbs/hbbr) reachable from your devices - For the in-browser client: TLS, and a proxy that bridges
wss://to the hbbs/hbbr WebSocket ports 21118 and 21119
sudo apt update
sudo apt install -y nginx mysql-server git unzip \
php8.4-fpm php8.4-mysql php8.4-mbstring php8.4-intl php8.4-bcmath \
php8.4-xml php8.4-curl php8.4-zip php8.4-gdIf your distribution ships an older PHP, add a PPA (Ubuntu: ondrej/php) or use
the Docker image instead.
The RustDesk server is a prerequisite, not part of this install. If you do not have one, or yours is only half-working, see RustDesk server in Docker — hbbs and hbbr from scratch, every port, and where the key comes from.
CREATE DATABASE cortendesk CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'cortendesk'@'localhost' IDENTIFIED BY 'use-a-real-password';
GRANT ALL PRIVILEGES ON cortendesk.* TO 'cortendesk'@'localhost';
FLUSH PRIVILEGES;sudo mkdir -p /opt/cortendesk
sudo chown "$USER" /opt/cortendesk
git clone https://github.com/marcpope/cortendesk.git /opt/cortendesk
cd /opt/cortendesk
composer install --no-dev --optimize-autoloadermain always equals the latest release. To pin a version:
git checkout v1.11.0cp .env.example .env
php artisan key:generateEdit .env:
APP_NAME=CortenDesk
APP_ENV=production
APP_DEBUG=false
APP_URL=https://console.example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=cortendesk
DB_USERNAME=cortendesk
DB_PASSWORD=use-a-real-password
# Your RustDesk server
CORTENDESK_ID_SERVER=hbbs.example.com:21116
CORTENDESK_RELAY_SERVER=hbbs.example.com:21117
CORTENDESK_PUBLIC_KEY=<contents of id_ed25519.pub>
# Native web client — wss endpoints your proxy exposes
CORTENDESK_NATIVE_WEBCLIENT=true
CORTENDESK_WS_ID_URL=wss://console.example.com/ws/id
CORTENDESK_WS_RELAY_URL=wss://console.example.com/ws/relay
APP_KEY encrypts the SMTP password, the OIDC client secret and 2FA secrets.
Back it up with the database — one without the other is not a usable restore.
Two optional settings since 1.11.0: CORTENDESK_UPDATE_CHECK=false stops the
GitHub release check and hides the update badge, and
CORTENDESK_NOTIFY_TIMEOUT (default 15 seconds) sets how long scheduled
notifications wait for Apprise.
php artisan migrate --seed
php artisan config:cache
php artisan route:cache
php artisan view:cache--seed creates admin / changeme. Change it at first sign-in.
The web server user needs to write two directories and nothing else:
sudo chown -R www-data:www-data /opt/cortendesk/storage /opt/cortendesk/bootstrap/cache
sudo find /opt/cortendesk/storage -type d -exec chmod 775 {} \;If you run artisan by hand afterwards, run it as www-data, or it will leave root-owned cache files that php-fpm cannot write:
sudo -u www-data php artisan config:cacheserver {
listen 443 ssl http2;
server_name console.example.com;
ssl_certificate /etc/letsencrypt/live/console.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/console.example.com/privkey.pem;
root /opt/cortendesk/public;
index index.php;
# Client installer uploads, up to 512 MB by default (CORTENDESK_DOWNLOADS_MAX_MB)
client_max_body_size 528m;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_read_timeout 300s; # copying and hashing a large upload
}
location ~ /\.(?!well-known).* {
deny all;
}
# Web client: bridge wss to the RustDesk server. Browsers refuse ws:// from
# an HTTPS page, which is the whole reason these two blocks exist.
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;
}
}
server {
listen 80;
server_name console.example.com;
return 301 https://$host$request_uri;
}Point proxy_pass at wherever hbbs/hbbr actually listen — 127.0.0.1 only if
they run on this same VM.
sudo nginx -t && sudo systemctl reload nginxSkip the two /ws/ blocks if you are not using the in-browser client. The
console itself works without them.
PHP has its own upload limits. Raise them to match nginx, then restart php-fpm:
; /etc/php/8.4/fpm/conf.d/99-cortendesk.ini
upload_max_filesize = 528M
post_max_size = 528MSee Client downloads.
Log retention, pruning and other maintenance run through Laravel's scheduler. Without this they never run.
sudo crontab -u www-data -e* * * * * cd /opt/cortendesk && php artisan schedule:run >> /dev/null 2>&1
If your PHP lists pcntl_signal in disable_functions (hosting panels often
do), use 1.10.1 or later. Earlier releases ran each scheduled task once and
then left it locked for a day.
On each RustDesk client, Settings → Network: ID Server, Relay Server, Key,
and API Server = https://console.example.com. Devices appear within about
fifteen seconds. The console's Settings screen shows copy-paste values for all
four fields.
For unattended deployment, create an API token under Settings → Security and use
the client's --assign flag.
If something else terminates TLS and forwards to this VM over plain HTTP, the
app honours X-Forwarded-* headers already. Set APP_URL to the public HTTPS
URL. If your proxy sits outside the private address ranges, set
TRUSTED_PROXIES in .env to its address — see
Reverse proxy and TLS.
Every heartbeat, inventory upload and console page takes a php-fpm worker
while it runs. Ubuntu's pool starts with pm.max_children = 5, which a few
hundred devices fill: requests queue in nginx and the console stops answering.
The Docker image uses 24 since 1.11.0. Set the same in
/etc/php/8.4/fpm/pool.d/www.conf if the VM has the memory (about 40 MB per
worker):
pm = dynamic
pm.max_children = 24
pm.start_servers = 6
pm.min_spare_servers = 3
pm.max_spare_servers = 12
pm.max_requests = 1000With MySQL, keep pm.max_children below the database's max_connections. If
the php-fpm log shows server reached pm.max_children setting, raise it.
nginx needs enough connections and open files for every device's requests.
The image uses 4096 connections per worker and twice that in open files. In
/etc/nginx/nginx.conf:
worker_rlimit_nofile 8192; # top level
events {
worker_connections 4096; # Ubuntu ships 768
}Restart php-fpm and reload nginx afterwards.
cd /opt/cortendesk
sudo -u www-data git pull
sudo -u www-data composer install --no-dev --optimize-autoloader
sudo -u www-data php artisan migrate --force
sudo -u www-data php artisan optimize:clear
sudo -u www-data php artisan config:cache
sudo -u www-data php artisan route:cache
sudo -u www-data php artisan view:cacheBack up the database first. See How to Upgrade.
mysqldump -u cortendesk -p cortendesk > cortendesk-$(date +%F).sql
cp /opt/cortendesk/.env /somewhere/safe/ # holds APP_KEYA database backup without APP_KEY cannot decrypt the SMTP password, the OIDC
client secret, or anyone's 2FA secret. Keep both.
php artisan cortendesk:import-lejianwen /path/to/rustdeskapi.db --dry-run
php artisan cortendesk:import-lejianwen /path/to/rustdeskapi.dbUsers keep their original password hashes, devices are deduplicated, and address books, share rules and audit history come across. Address-book entry passwords encrypted by the Go API cannot be decrypted and must be re-saved by users.
500 on every page. Almost always permissions. Check
storage/logs/laravel.log, then confirm storage and bootstrap/cache are
owned by www-data.
Changes to .env do nothing. The config is cached. Run
sudo -u www-data php artisan config:cache after every edit.
No devices appear. The clients' API Server is not pointing here. Confirm the value in the client, and that this host is reachable from the device.
Web client will not connect. The /ws/ blocks are missing, point at the
wrong host, or CORTENDESK_WS_*_URL is not the wss:// URL the browser sees.
Sign-in loops back to the login page, or 419 over HTTP. SESSION_SECURE_COOKIE=true
without HTTPS, or an APP_URL that does not match the address in the browser.
Locked out by SSO or 2FA. Set CORTENDESK_OIDC_DISABLED=true in .env, or
run sudo -u www-data php artisan cortendesk:2fa-reset <username>.
Notifications stopped and Diagnostics shows no fresh scheduler heartbeat,
though cron runs. Before 1.10.1, with pcntl_signal in PHP's
disable_functions, scheduled tasks ran once and then stayed locked. Upgrade,
then clear the stuck locks once:
sudo -u www-data php artisan schedule:clear-cache.
Uploads of large installers fail with 413 or an empty error. nginx
client_max_body_size or PHP upload_max_filesize/post_max_size is below
the file size. See step 6.