The topic I ditched laggy Linux remote desktop for this browser-based setup is currently the subject of lively discussion — readers and analysts are keeping a close eye on developments.

This is taking place in a dynamic environment: companies’ decisions and competitors’ reactions can quickly change the picture.

When I switch from Windows Remote Desktop to Linux, it always comes as a bit of a shock. That’s because the Remote Desktop Protocol (RDP) is native to Microsoft, but with Linux, it feels more improvised. Remoting into my Linux boxes often meant crashed sessions, poor resizing, and laggy performance. It also meant keeping an SSH session open just to restart the Xrdp service when it inevitably crashed.

I decided to give the open-source remote access gateway Apache Guacamole a try, but more specifically, a standalone installation on a new server where I could add and access my existing Linux boxes. I chose Guacamole because it allowed me to control an RDP session through a browser over HTTPS. It also solved most of my RDP performance and stability issues that all remote Linux users have faced at least a few times.

My homelab contains quite a few Linux boxes with a functional desktop. They all have their individual uses, but managing them all remotely poses a significant security risk given the many exposed remote access service ports. Of course, I could just throw them behind a mesh VPN like Tailscale, but that doesn’t solve the laggy performance and constant Xrdp service crashes. It’s also not as challenging or fun.

What I really wanted was to centrally access all my Linux desktops and resolve the Xrdp lag all in one place. Apache Guacamole lets me do exactly that. It lets me add my remote connections and launch them from a browser tab, and it takes care of the window sizing and performance settings behind the scenes. And the best part about Guacamole is that it means no more exposing remote desktop services to the public internet.

Browser → HTTPS on port 443 → Guacamole server → private RDP → Linux machines

The browser is the only client I need on any laptop or desktop. HTTPS on port 443 is a port forwarded rule in my network, allowing access to the gateway, with all other remote access ports being closed.

The Guacamole server acts as the gateway, storing connection profiles and granting access to my private Linux machines running RDP (remote desktop protocol).

Guacamole is installed in a small stack of three services. The Guacamole web app, guacd, and PostgreSQL. The web app provides the browser interface, guacd handles all the remote connections, and the PostgreSQL database handles user credentials and connection settings.

I installed Guacamole on a new virtual machine and assigned it an IP address of 192.168.100.10. A Guacamole gateway alone doesn’t require much in the way of resources, but I settled on these specs for the server:

Next was the Docker install, and this is where I ran into some trouble. The simple Ubuntu package routes didn’t give me the compose plugin I needed, so I used Docker’s official repo instead. After installing ca-certificates and curl, I created the key store and downloaded Docker’s official GPG signing key:

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

echo “deb [arch=$(dpkg –print-architecture) signed-by=/etc/apt/keyrings/docker.asc]

$(. /etc/os-release && echo “${UBUNTU_CODENAME:-$VERSION_CODENAME}”) stable” |

sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

My ideal setup was one where Guacamole was kept private on the server, with Nginx handling the public HTTPS:

I didn’t deviate much from the default Docker compose file. I mainly defined these three containers:

docker run –rm guacamole/guacamole /opt/guacamole/bin/initdb.sh –postgresql > initdb.sql

The standalone docker-compose (v1) is deprecated; Docker now recommends using the integrated docker compose plugin (Compose V2).

Once the containers were up and running, I deployed Nginx as a reverse proxy in front of Guacamole. Since Guacamole was only listening locally on 127.0.0.1:8080, Nginx became the gateway for accessing the web application.

Then I enabled the Nginx site and checked to make sure it was working:

sudo ln -s /etc/nginx/sites-available/guacamole /etc/nginx/sites-enabled/guacamole

Finally, I made it all legit with a TLS certificate for HTTPS:

That kept the Docker ports private and gave me a public URL to access the Guacamole login page at https://remote.ggcontentlabs.com/guacamole.

The goal of this project was to centralize remote access while also preventing my Linux desktops from being exposed to the internet. Basically, Guacamole became the front door, and everything else was hidden behind it.

The public side of my Apache Guacamole server was simple enough. Nginx was used as the reverse proxy and handled HTTPS on port 443. Let’s Encrypt and Certbot were used to create the free TLS certificate, and Guacamole sat behind all that. The Linux machines behind Guacamole could still be accessed locally on my LAN, but access to each machine was stopped by closing the remote access ports on my router.

The public-facing part of the Guacamole server firewall rules looked like this:

My Guacamole server runs in Docker, so I also needed to add its local subnet to my firewall rules. First, I checked for the Docker subnet:

sudo ufw allow from 172.18.0.0/16 to any port 3389 proto tcp

For each of my Linux desktops, I also added these incoming rules to allow the Guacamole server to access their respective remote services:

sudo ufw allow from 192.168.100.10 to any port 3389 proto tcp

sudo ufw allow from 192.168.100.10 to any port 5900 proto tcp

The next step and the final step was to start adding the Linux machines to Guacamole.

Once I created the new firewall rules, adding machines to Guacamole was really easy. The first step was to reset the default guacadmin password and create some new connections. In the admin panel, I went to Settings -> Connections -> New Connection. I gave each machine a friendly name, entered the hostname/IP address, selected the required protocol, and assigned a set of credentials for access.

The useful part is just how much control Guacamole gives you per connection. For an Xrdp desktop, I could set color depth, make it read-only, set a custom resolution, turn wallpaper on and off, enable and disable clipboard, file uploads, and remote printing, and set drive redirection, just to name a few.

The connection settings let me control settings I previously set in the RDP client itself, which usually resulted in terrible scaling, laggy performance, and the inevitable Xrdp service crash. For my first test machine, running the lightweight XFCE desktop, I used these settings:

With these settings now controlled by Guacamole, I could just connect, full-screen my browser, and experience a lag-free, issue-free remote desktop experience. If I need to remote into a new machine and keep working, it’s just a matter of opening a fresh tab. That gave me what I needed: a Linux desktop that resized with my browser window, a clean way to disconnect without orphaning a session, and no more babysitting the Xrdp-sesman service through SSH.

Yes, Guacamole adds another server to my collection, and I had to deal with Docker gotchas, TLS, and a few extra firewall rules along the way. It all means there are a few more moving parts just to get a remote desktop open on the run.

The payoff, though, is that Guacamole becomes my central remote access hub. Instead of exposing every SSH, VNC, and RDP port to the internet, I can keep those services private and let Guacamole handle access via a single HTTPS login.

That’s what made the setup pain worth it, because it was all up front and saved me from inadvertently exposing servers I care about to needless security risks later. Guacamole didn’t exactly solve the remote access mess, but it moved everything behind a single point that I could control.

For a home lab, small server fleet, or anyone who is frustrated by the lackluster Xrdp performance, the trade-off is completely worth it.