Apache Guacamole: Self-Hosted Remote Desktop Gateway Without a Client
Apache Guacamole is a clientless remote desktop gateway. You access your servers through a web browser — no plugins, no desktop clients, no Java applets. It supports SSH, VNC, RDP, and Telnet connections, all behind a single authenticated web interface.
Photo by Jeff Loucks on Unsplash
If you manage more than a couple of machines and find yourself juggling SSH terminals, RDP sessions, and VNC viewers, Guacamole consolidates all of that into one place.
Why Guacamole Over Direct Connections
The obvious question: why not just SSH directly or use a native RDP client?
A few reasons stand out in practice:
- Browser-based access from anywhere. You can reach your servers from a Chromebook, a tablet, or a locked-down corporate machine that won't let you install software.
- Single point of authentication. Instead of managing SSH keys or RDP credentials across multiple machines, users authenticate once with Guacamole.
- Session recording. Guacamole can record sessions for auditing — useful if you share server access with others.
- No exposed ports. Your SSH and RDP ports stay behind the firewall. Only Guacamole's web interface needs to be reachable.
Deploying With Docker Compose
The official Guacamole setup involves three components: the web application (guacamole/guacamole), the backend daemon (guacamole/guacd), and a database. Here is a Docker Compose configuration using PostgreSQL:
services:
guacd:
image: guacamole/guacd
container_name: guacd
restart: unless-stopped
postgres:
image: postgres:16
container_name: guacamole-db
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./db-data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
guacamole:
image: guacamole/guacamole
container_name: guacamole
restart: unless-stopped
depends_on:
- guacd
- postgres
environment:
GUACD_HOSTNAME: guacd
POSTGRESQL_HOSTNAME: postgres
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USER: guacamole_user
POSTGRESQL_PASSWORD: ${DB_PASSWORD}
ports:
- "8080:8080"
Before starting, you need to generate the database initialization script:
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgresql > init/initdb.sql
This creates the SQL schema that PostgreSQL will execute on first boot. Then bring everything up:
docker compose up -d
Navigate to http://your-server:8080/guacamole and log in with the default credentials guacadmin / guacadmin. Change these immediately.
Adding Your First Connection
In the Guacamole admin panel, go to Settings > Connections > New Connection. Here is what a typical SSH connection looks like:
- Name: homelab-proxmox
- Protocol: SSH
- Hostname: 192.168.1.100
- Port: 22
- Username: root
- Authentication: Private key (paste your key) or password
For RDP connections to a Windows machine:
- Protocol: RDP
- Hostname: 192.168.1.50
- Port: 3389
- Security mode: NLA (Network Level Authentication)
- Ignore server certificate: Enable this for self-signed certs on your LAN
VNC works the same way — specify the host, port (usually 5900+display number), and password.
Like what you're reading? Subscribe to Self-Hosted Weekly — free weekly guides in your inbox.
Organizing Connections With Groups
Once you have more than a handful of connections, create connection groups to keep things manageable. A practical structure:
├── Proxmox Hosts
│ ├── pve-01 (SSH)
│ ├── pve-02 (SSH)
├── VMs - Linux
│ ├── docker-host (SSH)
│ ├── media-server (SSH)
├── VMs - Windows
│ ├── win-workstation (RDP)
│ ├── win-server (RDP)
├── Network Gear
│ ├── switch-01 (SSH)
│ ├── firewall (SSH)
Groups can also be configured as "balancing groups" that distribute connections across multiple backends — useful for accessing a cluster of identical machines.
User Management and Access Control
Guacamole has a granular permission system. You can create users and control exactly which connections they can see and use. This is valuable when:
- You want to give a friend access to a game server without exposing your entire homelab
- Multiple family members need different levels of access
- You are running a small team and want to audit who connects where
Each user can be restricted to specific connections, prevented from creating new ones, and have their sessions recorded.
Putting It Behind a Reverse Proxy
Running Guacamole on port 8080 over HTTP is fine for initial setup, but for production use you want HTTPS. Here is an Nginx configuration:
server {
listen 443 ssl;
server_name guac.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/guac.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/guac.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:8080/guacamole/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The WebSocket upgrade headers are critical — without them, the remote desktop stream will not work. If you use Caddy or Traefik, both handle WebSocket proxying with less configuration.
Enabling Two-Factor Authentication
Guacamole supports TOTP (time-based one-time passwords) as a second factor. Add the TOTP extension to your Docker environment:
environment:
TOTP_ENABLED: "true"
After restarting, each user will be prompted to set up TOTP on their next login. Since Guacamole is often the single entry point to your infrastructure, enabling 2FA here is especially important.
Session Recording for Auditing
Enable session recording by adding parameters to individual connections:
- Recording path:
/recordings - Recording name:
${HISTORY_UUID} - Automatically create recording path: Yes
Mount a volume for /recordings in your guacd container. Recorded sessions are stored as raw Guacamole protocol data and can be replayed with the guacenc tool:
docker exec guacd guacenc /recordings/session-id.guac
This produces an M4V video file of the session.
Performance Tuning
A few settings that matter for a smooth experience:
- Color depth: For SSH connections, keep it at the default. For RDP/VNC, 16-bit color uses less bandwidth than 32-bit with minimal visual difference.
- Resize method: Set to "Display update" for RDP connections so the remote desktop adapts to your browser window size.
- Clipboard integration: Works out of the box for RDP and VNC. For SSH, use the on-screen clipboard panel (Ctrl+Alt+Shift).
- Drive redirection: Guacamole can map a server-side directory as a virtual drive in RDP sessions, letting you transfer files through the browser.
When Guacamole Might Not Be the Right Fit
Guacamole adds a layer between you and your machines. For latency-sensitive work like gaming or video editing over RDP, a native client will always perform better. If you only manage one or two Linux servers, plain SSH is simpler. And if your primary need is file transfer rather than interactive sessions, tools like SFTPGo or Filestash are more appropriate.
But for a homelab with a mix of Linux and Windows machines that you want to access from anywhere through a single web interface, Guacamole is hard to beat. It has been an Apache project since 2014, the community is active, and the Docker deployment is straightforward enough to get running in an afternoon.
