Remote desktop on a Linux VPS
Run a real graphical desktop on your VPS with xrdp and XFCE, tunnel it over SSH instead of opening port 3389, and see where RustDesk fits instead.
What remote desktop on a Linux VPS actually means
Two different products answer to the search "remote desktop on a Linux VPS", and picking the wrong one costs you an afternoon. The first is a remote-access broker. RustDesk's self-hosted server is the common example: it relays a session between two machines you already own, such as your laptop and the PC at home. The rented server draws no desktop at all. It introduces the two ends to each other and forwards packets when they cannot reach each other directly. The second is a real graphical desktop running on the rented server, so the pixels are drawn in the data centre and streamed to you. That is xrdp, VNC (virtual network computing), or a container workspace.
One question separates them. Once this is working, where does the mouse pointer live? On a machine you already own, you want a broker. On the VPS itself, you want a desktop on the VPS. The second case gets most of the space below, because it is the case most guides skip.
Which option fits your job
- RustDesk with your own relay. It protects the session from passing through a public rendezvous server run by strangers, because you hold the key pair. It does not protect the machine being controlled, which is still whatever PC you installed the client on, with whatever password that PC has.
- xrdp over an SSH tunnel or a VPN. It protects you from the constant internet-wide scanning of TCP 3389 and from password guessing against the RDP login box, because that port never faces the internet. It does not protect a weak account password from anyone who already has the tunnel.
- VNC over the same tunnel. It gives you a desktop session that survives disconnection, using a protocol older and simpler than RDP. On its own it protects nothing: the tunnel does all of the security work, so VNC alone on a public port is the worst option here.
- A container workspace such as Webtop or Kasm. It gives you a browser or a whole desktop in a container you can throw away and rebuild, which protects your real machine from whatever that browser touches. It does not protect the host: these images run with broad privileges and a passwordless
sudoinside, so the container is not a boundary you should trust with a hostile workload.
Install xrdp and XFCE on Ubuntu 24.04
A VPS server image ships with no graphical desktop. You install one, then install xrdp, the open-source server that speaks RDP (remote desktop protocol), the same protocol the Windows client speaks. Pick a light desktop, and XFCE is the usual answer.
sudo apt update
sudo apt install -y xrdp xorgxrdp xfce4 xfce4-goodies dbus-x11
systemctl is-active xrdpUbuntu 24.04 carries xrdp 0.9.24 and xorgxrdp in the universe component, as of August 2026. Install xorgxrdp by name even though it is only a recommended package: it is the X server backend that xrdp starts for a new session, and without it the login box accepts your password and then returns you straight to the login box.
Now tell the session which desktop to start. xrdp runs /etc/xrdp/startwm.sh, which runs ~/.xsession when that file exists.
echo "xfce4-session" > ~/.xsession
chmod 644 ~/.xsessionLast, xrdp needs to read the TLS (transport layer security) key it offers to clients. That file is mode 640 and owned by the ssl-cert group.
ls -l /etc/ssl/private/ssl-cert-snakeoil.key
id xrdpThe listing shows -rw-r----- 1 root ssl-cert. If id xrdp does not print ssl-cert among the groups, run sudo adduser xrdp ssl-cert then sudo systemctl restart xrdp. Skip that and xrdp cannot open the key, and /var/log/xrdp.log records the failure with the snakeoil filename in the line.
Why you should not open port 3389 to the internet
TCP 3389 is scanned continuously by everything on the internet, and an RDP login box answers politely to every password attempt. Do not open it. Bind xrdp to the loopback address instead, and reach it through a tunnel you already trust.
Edit /etc/xrdp/xrdp.ini and change the listener in the [Globals] section.
[Globals]
port=tcp://.:3389The shipped file documents that syntax in its own comments: tcp://.:3389 means 127.0.0.1:3389, and tcp://:3389 means every interface. Restart and confirm, because a typo here silently leaves the service on all addresses.
sudo systemctl restart xrdp
ss -tlnp | grep 3389You want 127.0.0.1:3389. Seeing 0.0.0.0:3389 means xrdp ignored your edit, usually because the line ended up under a different section heading further down the file.
Now open the tunnel from your own machine.
ssh -N -L 3389:127.0.0.1:3389 you@vps.example.com-N means "open the connection but run no command", so the session exists only to carry the port. Leave that terminal running and point the RDP client at 127.0.0.1:3389. On a Linux client the software is FreeRDP 3, whose binary on Ubuntu 24.04 is named xfreerdp3:
sudo apt install -y freerdp3-x11
xfreerdp3 /v:127.0.0.1:3389 /u:you /dynamic-resolution +clipboard /soundOn Windows use the built-in mstsc and type 127.0.0.1 as the computer. FreeRDP asks you to trust the certificate on the first connect and prints Do you trust the above certificate? (Y/T/N), which is expected with the self-signed snakeoil certificate.
If ssh answers bind [127.0.0.1]:3389: Address already in use, something on your own machine already holds 3389. Move the local end with ssh -N -L 13389:127.0.0.1:3389 you@vps.example.com and connect to 127.0.0.1:13389.
One tunnel per person gets tiring, so for a team the better answer is a private network. Put the box behind a self-hosted WireGuard VPN, give it the tunnel address 10.8.0.1, and set port=tcp://10.8.0.1:3389 so xrdp answers only inside the VPN. Either way, the firewall rule for 3389 should not exist at all. If you are unsure what your current rules allow, start from ufw firewall basics on a VPS and check before you connect, not after.
How much RAM does a remote desktop use on a 2 GB VPS
The desktop you choose decides whether a 2 GB plan is comfortable or hopeless. The figures below are rounded typical numbers for memory in use just after login on Ubuntu 24.04, taken from published comparisons rather than measured on your machine. Measure your own with free -m right after you connect.
The data behind this chart
[
{
"label": "LXQt",
"idle_ram_mb": 300
},
{
"label": "XFCE",
"idle_ram_mb": 400
},
{
"label": "MATE",
"idle_ram_mb": 500
},
{
"label": "KDE Plasma",
"idle_ram_mb": 800
},
{
"label": "GNOME",
"idle_ram_mb": "1,200"
}
]Across those 5 desktops the spread is the point. LXQt sits near 300 MB and XFCE near 400 MB, so either leaves room on a 2 GB box for a browser. GNOME wants roughly 1,200 MB before you open a single window, which on 2 GB leaves the browser fighting the desktop for what remains.
The browser is the real cost, not the desktop shell. A modern browser spends somewhere between 150 and 400 MB per active tab, so a 2 GB VPS running XFCE handles a handful of tabs and then starts swapping. Add swap so the machine slows down instead of killing processes: sudo fallocate -l 2G /swapfile, then sudo chmod 600 /swapfile, sudo mkswap /swapfile, sudo swapon /swapfile, and a matching line in /etc/fstab so it survives a reboot. When something vanishes with no warning, run dmesg | grep -i "killed process". That line means the kernel out-of-memory killer ended it, and the browser is the usual victim.
CPU is the other limit, and it is easier to underestimate. A VPS has no GPU, so X falls back to software rendering through llvmpipe, which means the CPU draws every pixel. Scrolling a heavy page and playing video both show up as plain CPU load, and the frame rate drops rather than the machine freezing. That is the same wall you meet if you were wondering whether you can game on a VPS: for anything 3D the answer is no, and for exactly this reason.
Sound and clipboard in an xrdp session
Ubuntu 24.04 uses PipeWire for audio, and xrdp's sound redirection was written against PulseAudio, so a fresh install gives you working video and silence. Ubuntu packages the bridge.
sudo apt install -y pipewire-module-xrdp pulseaudio-utils alsa-utilsLog out of the RDP session completely and log in again, because the module is loaded when the session starts. A reconnect is not enough. Then check from inside the session:
pactl list short sinks
speaker-test -c 2 -t wav -l 1You should see a sink whose name mentions xrdp, and hear the test tone through your client. No xrdp sink means the module did not load into this session. Your client also has to ask for audio: that is the /sound flag on xfreerdp3, or the "Remote audio" setting under Local Resources in the Windows client.
Text clipboard works in both directions once xrdp-chansrv is running for your session, which xrdp starts for you. Confirm it with pgrep -a xrdp-chansrv. If copy and paste stops working part way through a session, that process died, and reconnecting restarts it. Copying files rather than text is a separate channel called drive redirection: /drive:home,/home/you on xfreerdp3 mounts a local folder into the remote session.
The polkit popup, and other first-login failures
The most common first-login surprise is a dialog reading Authentication is required to create a color managed device. The cause is specific. The colord service asks polkit for permission, polkit grants that action silently only to a session it considers locally seated, and an RDP session is not seated, so polkit falls back to asking you for a password. Ubuntu 24.04 ships polkit 124, which removed the old local authority .pkla files, so every guide telling you to write /etc/polkit-1/localauthority/50-local.d/45-allow-colord.pkla has no effect at all on 24.04. Write a JavaScript rule instead.
/* /etc/polkit-1/rules.d/45-allow-colord.rules */
polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.color-manager.") === 0 &&
subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
});Run sudo systemctl restart polkit and reconnect. Two other failures are worth knowing by their symptom.
The login box takes your password and comes straight back. The session started and died. Read /var/log/xrdp-sesman.log first, then ~/.xsession-errors in your home directory. A missing xorgxrdp, a ~/.xsession naming a desktop that is not installed, a home directory you cannot write to, or a full disk all end here.
You connect and see a grey screen with an X cursor. X started and the desktop did not. That is ~/.xsession again: run xfce4-session by hand over SSH and read the error it prints.
What the self-hosted RustDesk server does
RustDesk splits into two processes. hbbs is the ID and rendezvous server that clients register with, and hbbr is the relay that carries the session when a direct peer-to-peer connection fails. Neither one runs a desktop. Both come from one image, and this is the compose file the project publishes, with the relay address changed to your own host name:
services:
hbbs:
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs -r rustdesk.example.com:21117
ports:
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21118:21118
volumes:
- ./data:/root
restart: unless-stopped
hbbr:
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr
ports:
- 21117:21117
- 21119:21119
volumes:
- ./data:/root
restart: unless-stoppedBring it up, then read the public key the server generated on its first start:
sudo docker compose up -d
sudo cat ./data/id_ed25519.pubEvery client needs your host name and that public key, both entered under Network settings in the RustDesk client. The matching private key stays in ./data/id_ed25519. Delete the data directory and the server generates a fresh pair, so every client then has to be reconfigured with the new key. Back that directory up.
The firewall has to allow these ports directly. hbbs uses TCP 21115, 21116 and 21118, plus UDP 21116. hbbr uses TCP 21117 and 21119.
sudo ufw allow 21115/tcp
sudo ufw allow 21116/tcp
sudo ufw allow 21116/udp
sudo ufw allow 21117/tcp
sudo ufw allow 21118/tcp
sudo ufw allow 21119/tcpWhy RustDesk will not go behind nginx or Traefik
Readers who already terminate TLS for everything at one reverse proxy try this and fail. hbbs and hbbr speak their own binary protocols over TCP and UDP, not HTTP. There is no Host header to route on and no HTTP request to inspect, so an nginx server block or a Traefik HTTP router has nothing to match. The UDP listener on 21116 is not an HTTP thing at any layer.
Two things do work. nginx can forward the TCP ports with a stream block, which is plain layer 4 forwarding rather than a reverse proxy in the usual sense. And ports 21118 and 21119 carry the websockets used by the RustDesk web client, which is ordinary HTTP, so those two can sit behind your proxy. If you do that, add firewall rules so only the proxy can reach 21118 and 21119, because hbbs trusts the X-Real-IP header on websocket connections to work out the real client address.
A disposable browser in a container
Sometimes all you want is a clean browser with an IP that does not change, kept away from your own machine. A container workspace does that with far less software installed. LinuxServer's Webtop is the light option:
services:
webtop:
image: lscr.io/linuxserver/webtop:latest
container_name: webtop
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
volumes:
- /path/to/data:/config
ports:
- 127.0.0.1:3000:3000
- 127.0.0.1:3001:3001
shm_size: "1gb"
restart: unless-stoppedPort 3000 serves HTTP and 3001 serves HTTPS, and you reach the desktop in a browser tab with no RDP client anywhere. Image tags cover XFCE, KDE, MATE and i3 on several base distributions. The project's own documentation is blunt about the risk: the container has privileged access to the host and includes a terminal with passwordless sudo, so it should not face the internet unprotected. That is why the ports above are bound to 127.0.0.1 rather than published on all addresses. Reach it through the same SSH tunnel or the same VPN you used for xrdp.
Kasm Workspaces is the same idea at a much larger size, with a web console, user accounts, and per-session containers that reset when the session ends. It needs more machine than a small VPS has. As of August 2026 the documented minimum is 2 CPU cores, 4 GB of memory and 50 GB of SSD, and each user session defaults to 2 cores and 2768 MB on top of that. A 2 GB plan will not run it. The install is a download and a script:
cd /tmp
curl -O https://kasm-static-content.s3.amazonaws.com/kasm_release_1.17.0.7f020d.tar.gz
tar -xf kasm_release_1.17.0.7f020d.tar.gz
sudo bash kasm_release/install.shVNC, and where it still fits
VNC sends framebuffer updates rather than drawing commands, so it feels heavier than RDP on a slow link and it carries no sound channel. It earns its place in one situation: you want a desktop session that keeps running after you disconnect, and you want the same session back when you return. TigerVNC does that. vncserver -localhost yes :1 binds Xvnc to 127.0.0.1 on TCP 5901, and refuses connections from anywhere else, so you tunnel it exactly like xrdp with ssh -N -L 5901:127.0.0.1:5901 you@vps.example.com. Never publish a VNC port. Most VNC servers protect the password during the handshake and nothing after it, so on a public port the session contents are readable on the wire.
Is a VPS a good desktop?
As a daily driver, no, and the reasons stack up. There is no GPU, so the CPU draws everything. Every keystroke waits for a network round trip, and 40 ms of latency that feels fine in SSH is noticeable in a text editor. Video is compressed twice, once by the site and again by the RDP encoder. Your files sit on a disk you do not hold, and heavy desktop use eats a monthly bandwidth allowance that was sized for a web server.
As a disposable machine it is very good, and the same properties explain why. The IP address is stable and belongs to a data centre, which is what you want when a service must see a consistent address. The machine rebuilds from an image in minutes, so a session that picked up something nasty costs you nothing. It is isolated from your real hardware, and it keeps running when your laptop is closed. Hourly billing makes a throwaway desktop cheap.
If you are still working out what the box is for, the practical list of what a VPS is good at is worth reading before you install a desktop on it. And if the reason you wanted a desktop was one Windows application, weigh that against the real differences between Linux and Windows Server first, because the licence changes the cost of the answer.
FAQ
Can I run a remote desktop on a 2 GB VPS?
Yes, with a light desktop. XFCE or LXQt uses roughly 300 to 400 MB after login, which leaves enough for a browser with a few tabs. GNOME or KDE Plasma on 2 GB leaves almost nothing for applications. Add a 2 GB swap file so memory pressure slows the machine instead of killing processes, and when something disappears with no message, check dmesg | grep -i "killed process" for the kernel out-of-memory killer.
Should I open port 3389 on my VPS firewall?
No. TCP 3389 is scanned constantly and an exposed RDP login box invites password guessing. Set port=tcp://.:3389 in /etc/xrdp/xrdp.ini so xrdp listens on 127.0.0.1 only, confirm it with ss -tlnp | grep 3389, and reach it with ssh -N -L 3389:127.0.0.1:3389 you@vps.example.com. For more than one or two people, bind xrdp to a WireGuard address instead of loopback.
Why does xrdp ask "Authentication is required to create a color managed device"?
The colord service asks polkit for permission, and polkit grants that action silently only to a locally seated session. An RDP session is not seated, so you get a password prompt at every login. On Ubuntu 24.04 the old .pkla fix does nothing, because polkit 124 dropped the local authority files. Create /etc/polkit-1/rules.d/45-allow-colord.rules holding a JavaScript rule that returns polkit.Result.YES for action ids starting with org.freedesktop.color-manager., then run sudo systemctl restart polkit.
Can I put a self-hosted RustDesk server behind nginx or Traefik?
Not the main service. hbbs and hbbr use their own binary protocols rather than HTTP, so there is no Host header to route on, and UDP 21116 cannot cross an HTTP proxy at all. Open TCP 21115 through 21119 and UDP 21116 on the firewall and let clients connect directly. The websocket ports 21118 and 21119, used by the web client, are HTTP and can sit behind a proxy. If they do, firewall them so only the proxy reaches them, because hbbs trusts X-Real-IP on those connections.
Why is there no sound in my xrdp session?
Ubuntu 24.04 uses PipeWire while xrdp's sound redirection was built for PulseAudio, so audio is missing until you install the bridge. Run sudo apt install -y pipewire-module-xrdp, then log out of the session completely and log in again, because the module is loaded at session start and a reconnect will not load it. Check with pactl list short sinks for a sink naming xrdp, and make sure the client requests audio, which is the /sound flag on xfreerdp3 or "Remote audio" in the Windows client.