Apache Guacamole Remote Access Gateway

Apache Guacamole is a clientless remote desktop gateway that provides browser-based access to RDP, VNC, and SSH connections without requiring any client software. This guide covers deploying Guacamole with Docker, setting up connections, configuring LDAP authentication, and enabling two-factor security.

Prerequisites

  • Linux server (Ubuntu 22.04/Debian 12 or CentOS/Rocky 9) with at least 2 GB RAM
  • Docker Engine and Docker Compose v2
  • A domain name with a valid TLS certificate
  • Ports 80 and 443 open in firewall
  • Network access from the Guacamole server to target machines (RDP/3389, SSH/22, VNC/5900)

Deploy Guacamole with Docker Compose

Guacamole consists of two components:

  • guacd - the daemon that handles remote protocol connections
  • guacamole - the web application and REST API
mkdir -p /opt/guacamole/{mysql,init}
cd /opt/guacamole

# Generate the MySQL initialization script from the Guacamole image
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --mysql > init/initdb.sql

cat > docker-compose.yml << 'EOF'
version: "3.8"

services:
  # Guacamole backend daemon
  guacd:
    image: guacamole/guacd:latest
    restart: unless-stopped
    volumes:
      - guacd_data:/tmp/guacd

  # MySQL database for user/connection storage
  mysql:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: StrongRootPassword123
      MYSQL_DATABASE: guacamole_db
      MYSQL_USER: guacamole_user
      MYSQL_PASSWORD: StrongGuacPassword456
    volumes:
      - ./mysql:/var/lib/mysql
      - ./init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Guacamole web application
  guacamole:
    image: guacamole/guacamole:latest
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      GUACD_HOSTNAME: guacd
      GUACD_PORT: 4822
      MYSQL_HOSTNAME: mysql
      MYSQL_PORT: 3306
      MYSQL_DATABASE: guacamole_db
      MYSQL_USER: guacamole_user
      MYSQL_PASSWORD: StrongGuacPassword456
      # Optional: set a custom path
      # WEBAPP_CONTEXT: ROOT
    ports:
      - "8080:8080"

volumes:
  guacd_data:
EOF

docker compose up -d

# Check container health
docker compose ps
docker compose logs guacamole --tail=30

Initial Configuration and Admin Setup

  1. Access the Guacamole interface at http://your-server:8080/guacamole
  2. Log in with default credentials: admin / guacadmin
  3. Immediately change the admin password:
    • Click the username (top right) > Settings > Preferences
    • Change the password
# Verify the web interface is running
curl -s http://localhost:8080/guacamole/api/tokens | python3 -m json.tool

Configure RDP Connections

Add a Windows Remote Desktop connection through the admin interface:

  1. Go to Settings > Connections > New Connection
  2. Select Protocol: RDP
  3. Configure:
Name:           Windows Server
Location:       ROOT
Protocol:       RDP

Network:
  Hostname:     192.168.1.50
  Port:         3389

Authentication:
  Username:     Administrator
  Password:     (leave blank to prompt user)
  Domain:       EXAMPLE

Display:
  Color depth:  True color (32-bit)
  Width:        1920
  Height:       1080
  DPI:          96

Performance:
  Enable wallpaper:    false
  Enable font smoothing: true
  Enable full window drag: false

Security:
  Security mode:   Any
  Ignore cert:     true (for self-signed certs)

For NLA (Network Level Authentication):

Security mode:   NLA
Ignore cert:     false   # Use proper cert in production

Configure SSH Connections

Add an SSH connection for Linux servers:

  1. Settings > Connections > New Connection > Protocol: SSH
Name:           Linux Server
Protocol:       SSH

Network:
  Hostname:     192.168.1.100
  Port:         22

Authentication:
  Username:     admin
  Private key:  (paste SSH private key)
  Passphrase:   (if key is encrypted)

Terminal:
  Font size:    12
  Color scheme: Gray black
  Backspace key: Delete

For key-based SSH, prepare the key:

# Generate a dedicated SSH key for Guacamole
ssh-keygen -t ed25519 -f /tmp/guacamole_key -N ""

# Add public key to target server
ssh-copy-id -i /tmp/guacamole_key.pub [email protected]

# Paste the private key content into the Guacamole connection settings
cat /tmp/guacamole_key

Configure VNC Connections

  1. Settings > Connections > New Connection > Protocol: VNC
Name:           Desktop 01
Protocol:       VNC

Network:
  Hostname:     192.168.1.200
  Port:         5901     (5900 + display number)

Authentication:
  Password:     (VNC password)

Display:
  Color depth:  True color (32-bit)
  Cursor:       Remote (server-side)

For VNC with SSH tunneling (recommended for security):

Hostname:       192.168.1.200
Port:           5901

SSH Tunnel:
  SSH Host:     192.168.1.200
  SSH Port:     22
  SSH Username: admin
  SSH Private Key: (paste key)

LDAP Authentication

Configure Guacamole to authenticate against LDAP/Active Directory:

# Update docker-compose.yml - add LDAP environment variables to guacamole service
sudo nano /opt/guacamole/docker-compose.yml

Add to the guacamole service environment:

environment:
  # ... existing settings ...
  
  # LDAP Configuration
  LDAP_HOSTNAME: ldap.example.com
  LDAP_PORT: 389
  LDAP_ENCRYPTION_METHOD: starttls   # or "ssl" for LDAPS
  LDAP_USER_BASE_DN: "ou=Users,dc=example,dc=com"
  LDAP_USERNAME_ATTRIBUTE: uid
  LDAP_SEARCH_BIND_DN: "cn=guacamole-reader,ou=ServiceAccounts,dc=example,dc=com"
  LDAP_SEARCH_BIND_PASSWORD: ServiceAccountPassword
  LDAP_GROUP_BASE_DN: "ou=Groups,dc=example,dc=com"
  LDAP_GROUP_SEARCH_FILTER: "(objectClass=groupOfNames)"
  LDAP_MEMBER_ATTRIBUTE: member
docker compose up -d guacamole

# Test LDAP login
# Log in with an LDAP user - Guacamole will create the user automatically on first login

For Active Directory:

LDAP_HOSTNAME: dc.example.com
LDAP_PORT: 389
LDAP_USER_BASE_DN: "dc=example,dc=com"
LDAP_USERNAME_ATTRIBUTE: sAMAccountName   # Windows login name
LDAP_SEARCH_BIND_DN: "cn=guac-reader,cn=Users,dc=example,dc=com"
LDAP_SEARCH_BIND_PASSWORD: Password
LDAP_GROUP_BASE_DN: "cn=Users,dc=example,dc=com"

Two-Factor Authentication

Enable TOTP (Time-based One-Time Password) for MFA:

# Add to guacamole service environment in docker-compose.yml
TOTP_ENABLED: "true"
TOTP_ISSUER: "Guacamole"   # Displayed in authenticator app
TOTP_DIGITS: 6
TOTP_PERIOD: 30
docker compose up -d guacamole

Users will be prompted to enroll their TOTP device on next login by scanning a QR code with Google Authenticator or Authy.

For Duo Security MFA:

DUO_API_HOSTNAME: api-XXXXXXXX.duosecurity.com
DUO_INTEGRATION_KEY: DIXXXXXXXXXXXXXXXXXX
DUO_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DUO_APPLICATION_KEY: (generate with: python3 -c "import hashlib, os; print(hashlib.sha1(os.urandom(32)).hexdigest())")

Reverse Proxy with Nginx

Put Guacamole behind Nginx with HTTPS:

sudo tee /etc/nginx/sites-available/guacamole << 'EOF'
server {
    listen 80;
    server_name guac.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name guac.example.com;

    ssl_certificate     /etc/letsencrypt/live/guac.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/guac.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_buffering          off;
        proxy_http_version       1.1;
        proxy_set_header         Host $host;
        proxy_set_header         Upgrade $http_upgrade;
        proxy_set_header         Connection $http_connection;
        proxy_set_header         X-Real-IP $remote_addr;
        proxy_set_header         X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header         X-Forwarded-Proto $scheme;
        
        # Required for WebSocket (Guacamole uses WebSockets)
        proxy_read_timeout       3600;
        proxy_send_timeout       3600;
        
        # Remove the /guacamole prefix if Guacamole is at the root
        proxy_pass_header        Server;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/guacamole /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Remove Guacamole's port from Docker Compose (only expose via Nginx):

guacamole:
  # Change from:
  ports:
    - "8080:8080"
  # To:
  ports:
    - "127.0.0.1:8080:8080"

Troubleshooting

"Cannot connect to Guacamole server" in browser:

# Check all containers are running
docker compose ps
docker compose logs guacamole --tail=50

# Verify guacd is running
docker compose logs guacd --tail=20

# Check database connection
docker compose exec guacamole cat /opt/guacamole/bin/start.sh

RDP connections fail with "Unable to connect":

# Verify the target is reachable from the Guacamole container
docker compose exec guacamole ping -c3 192.168.1.50
docker compose exec guacamole nc -zv 192.168.1.50 3389

# Check RDP is enabled on the target Windows server:
# Control Panel > System > Remote Settings > Allow remote connections

SSH authentication failing:

# Test SSH directly from server
ssh -i /tmp/guacamole_key [email protected]

# Verify key format - Guacamole requires OpenSSH private key format
# Convert from PuTTY format if needed:
puttygen guacamole.ppk -O private-openssh -o guacamole_key

LDAP users can't log in:

# Test LDAP bind manually
ldapsearch -H ldap://ldap.example.com -D "cn=guacamole-reader,ou=ServiceAccounts,dc=example,dc=com" \
  -w ServiceAccountPassword -b "ou=Users,dc=example,dc=com" "(uid=testuser)"

# Check Guacamole logs for LDAP errors
docker compose logs guacamole 2>&1 | grep -i ldap

Conclusion

Apache Guacamole provides a secure, browser-based remote access gateway that eliminates the need for VPN clients or native RDP/SSH applications on end-user devices. Docker Compose deployment simplifies installation and updates, while LDAP integration enables centralized user management and TOTP adds an essential MFA layer for any internet-exposed gateway. Ensure Guacamole is always placed behind a TLS-terminating reverse proxy and restrict access using firewall rules to limit the attack surface.