Apache Guacamole es una pasarela de escritorio remoto que funciona desde el navegador: los usuarios abren una página web y se conectan a servidores por SSH, RDP o VNC sin instalar ningún cliente. Solo el servidor de Guacamole necesita alcanzar los equipos de destino, así que no hace falta exponer los puertos 22 o 3389 a Internet. En este tutorial desplegarás Guacamole 1.6 con Docker Compose y PostgreSQL en Ubuntu 24.04, lo publicarás detrás de Nginx con un certificado de Let's Encrypt, activarás el doble factor TOTP y crearás tus primeras conexiones.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, al menos 2 GB de RAM y 2 vCPU, por ejemplo un VPS de CubePath. Cada sesión RDP activa consume memoria en guacd, así que calcula más para muchos usuarios simultáneos.
  • Un usuario no root con privilegios sudo que pertenezca al grupo docker.
  • Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker.
  • Un dominio o subdominio (en la guía, your_domain) con un registro A apuntando a la IP pública del servidor.
  • Conectividad de red desde este servidor hasta los equipos a los que quieras acceder (por red privada o por Internet).

Paso 1: Preparar el directorio y los secretos

Crea el directorio del proyecto y haz que pertenezca a tu usuario:

sudo mkdir -p /opt/guacamole/initdb
sudo chown -R "$USER": /opt/guacamole
cd /opt/guacamole

Genera una contraseña aleatoria para la base de datos y guárdala en un archivo .env, que Docker Compose lee automáticamente:

echo "POSTGRES_PASSWORD=$(openssl rand -base64 24)" > .env
chmod 600 .env

Paso 2: Generar el esquema de la base de datos

La imagen de Guacamole incluye un script que imprime el SQL necesario para crear las tablas. Guárdalo en initdb/, que PostgreSQL ejecutará automáticamente la primera vez que arranque con un volumen vacío:

docker run --rm guacamole/guacamole:1.6.0 /opt/guacamole/bin/initdb.sh --postgresql > initdb/initdb.sql

Comprueba que el archivo contiene las tablas:

grep -c "CREATE TABLE" initdb/initdb.sql
23

El número exacto puede variar entre versiones; lo importante es que no sea cero.

Paso 3: Crear el archivo de Docker Compose

El despliegue tiene tres contenedores:

  • guacd: el demonio que habla SSH, RDP y VNC con los equipos de destino.
  • postgres: guarda usuarios, conexiones, permisos e historial.
  • guacamole: la aplicación web (Tomcat) que usan los navegadores.

Crea el archivo:

nano /opt/guacamole/compose.yaml
services:
  guacd:
    image: guacamole/guacd:1.6.0
    restart: unless-stopped

  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: guacamole_db
      POSTGRES_USER: guacamole_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./initdb:/docker-entrypoint-initdb.d:ro
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U guacamole_user -d guacamole_db"]
      interval: 10s
      timeout: 5s
      retries: 5

  guacamole:
    image: guacamole/guacamole:1.6.0
    restart: unless-stopped
    depends_on:
      guacd:
        condition: service_started
      postgres:
        condition: service_healthy
    environment:
      GUACD_HOSTNAME: guacd
      POSTGRESQL_HOSTNAME: postgres
      POSTGRESQL_DATABASE: guacamole_db
      POSTGRESQL_USERNAME: guacamole_user
      POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
      REMOTE_IP_VALVE_ENABLED: "true"
    ports:
      - "127.0.0.1:8080:8080"

volumes:
  pgdata:

Dos detalles importantes:

  • El puerto 8080 solo se publica en 127.0.0.1. Los usuarios entrarán por Nginx con HTTPS, nunca directamente a Tomcat. Además, los puertos que Docker publica en todas las interfaces se saltan las reglas de UFW, así que limitarlo a localhost es necesario.
  • REMOTE_IP_VALVE_ENABLED hace que Guacamole registre la IP real del usuario (enviada por Nginx en X-Forwarded-For) en lugar de la del proxy.

Paso 4: Arrancar Guacamole

Descarga las imágenes y levanta los servicios:

docker compose up -d
docker compose ps
NAME                     IMAGE                       SERVICE     STATUS                    PORTS
guacamole-guacamole-1    guacamole/guacamole:1.6.0   guacamole   Up 20 seconds             127.0.0.1:8080->8080/tcp
guacamole-guacd-1        guacamole/guacd:1.6.0       guacd       Up 31 seconds             4822/tcp
guacamole-postgres-1     postgres:16                 postgres    Up 31 seconds (healthy)

Comprueba que la aplicación responde y que ha cargado la autenticación con PostgreSQL:

curl -sI http://127.0.0.1:8080/guacamole/ | head -n 1
docker compose logs guacamole | grep -i "postgresql"
HTTP/1.1 200
... Extension "PostgreSQL Authentication" (postgresql) loaded.

Si no aparece esa línea, revisa las variables POSTGRESQL_* y los logs completos con docker compose logs guacamole.

Paso 5: Publicar Guacamole con Nginx y HTTPS

Instala Nginx y Certbot desde los repositorios de Ubuntu:

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

Crea el sitio de Nginx. Guacamole usa WebSocket para las sesiones, por eso se reenvían las cabeceras Upgrade y Connection y se desactiva el buffering:

sudo nano /etc/nginx/sites-available/guacamole
server {
    listen 80;
    listen [::]:80;
    server_name your_domain;

    location = / {
        return 301 /guacamole/;
    }

    location /guacamole/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_buffering off;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_read_timeout 1h;
        client_max_body_size 1g;
        access_log off;
    }
}

client_max_body_size permite subir archivos a las sesiones y proxy_read_timeout evita que Nginx corte sesiones con poca actividad.

Activa el sitio, desactiva el sitio por defecto y comprueba la sintaxis:

sudo ln -s /etc/nginx/sites-available/guacamole /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Abre en el cortafuegos SSH y los puertos 80 y 443:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Obtén el certificado. Certbot modifica el sitio para servirlo por HTTPS y redirigir HTTP:

sudo certbot --nginx -d your_domain

Comprueba la renovación automática:

sudo certbot renew --dry-run

Paso 6: Sustituir la cuenta de administrador por defecto

Abre https://your_domain/ en el navegador. Serás redirigido a /guacamole/ y verás la pantalla de inicio de sesión. La base de datos se crea con el usuario guacadmin y la contraseña guacadmin, que debes eliminar cuanto antes:

  1. Inicia sesión con guacadmin / guacadmin.
  2. Abre el menú de usuario (arriba a la derecha) y entra en Settings > Users > New User.
  3. Escribe tu nombre de usuario y una contraseña fuerte, marca todos los permisos de la sección Permissions (incluido Administer system) y guarda.
  4. Cierra sesión, entra con el nuevo usuario y, en Settings > Users, abre guacadmin y pulsa Delete.

Paso 7: Activar la autenticación de doble factor (TOTP)

La imagen oficial incluye la extensión TOTP; basta con activarla con una variable de entorno. Añádela al servicio guacamole en compose.yaml:

nano /opt/guacamole/compose.yaml
    environment:
      GUACD_HOSTNAME: guacd
      POSTGRESQL_HOSTNAME: postgres
      POSTGRESQL_DATABASE: guacamole_db
      POSTGRESQL_USERNAME: guacamole_user
      POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
      REMOTE_IP_VALVE_ENABLED: "true"
      TOTP_ENABLED: "true"

Recrea el contenedor para aplicar el cambio:

docker compose up -d

En el siguiente inicio de sesión, Guacamole mostrará un código QR para registrar el dispositivo en una aplicación de autenticación (Aegis, Google Authenticator, 1Password...). A partir de ahí, cada acceso pedirá la contraseña y el código de 6 dígitos. Si un usuario pierde el dispositivo, un administrador puede reiniciar su registro TOTP desde la ficha del usuario en Settings > Users, en la sección de atributos de TOTP.

Paso 8: Crear conexiones SSH y RDP

Las conexiones se gestionan en Settings > Connections > New Connection.

Para un servidor Linux por SSH:

  1. Name: un nombre descriptivo, por ejemplo web-01 (SSH). Protocol: SSH.
  2. En Parameters > Network: Hostname con la IP privada o el nombre del servidor y Port 22.
  3. En Authentication: el Username y, preferiblemente, una Private key en formato OpenSSH en lugar de una contraseña. Si dejas vacíos usuario y contraseña, Guacamole los pedirá al conectar.
  4. Guarda.

Para un servidor Windows por RDP:

  1. Protocol: RDP. Hostname: la IP del servidor. Port: 3389.
  2. En Authentication: Username, Password y, si pertenece a un dominio, Domain.
  3. Security mode: NLA (Network Level Authentication). Si el servidor usa un certificado autofirmado, marca Ignore server certificate.
  4. Guarda.

Vuelve a la pantalla de inicio (menú de usuario > Home) y haz clic en la conexión. Se abrirá la sesión en la misma pestaña. Pulsa Ctrl+Alt+Shift para abrir el menú lateral de Guacamole, desde el que puedes copiar y pegar texto o transferir archivos.

Asigna las conexiones a usuarios o grupos en Settings > Users o Settings > Groups, dentro de la sección Connections de cada uno. Así cada persona solo ve los equipos que le corresponden.

Paso 9: Copias de seguridad y actualizaciones

Toda la configuración está en PostgreSQL. Haz una copia con pg_dump:

cd /opt/guacamole
docker compose exec -T postgres pg_dump -U guacamole_user guacamole_db | gzip > "guacamole-$(date +%F).sql.gz"
ls -lh guacamole-*.sql.gz

Guarda esas copias fuera del servidor. Para actualizar a una versión nueva, lee primero las notas de la versión: algunas incluyen scripts de actualización del esquema (se publican junto a la extensión JDBC en la página de descargas de cada versión) que debes aplicar a la base de datos antes de cambiar los tags de guacd y guacamole en compose.yaml y ejecutar docker compose pull && docker compose up -d.

Solución de problemas

La página carga pero las sesiones se quedan en "Connecting" o se desconectan. Normalmente falta el soporte de WebSocket en el proxy. Comprueba que el bloque location /guacamole/ contiene las cabeceras Upgrade y Connection y proxy_http_version 1.1.

Error "Connection error" al abrir una conexión. guacd no alcanza el equipo de destino o la autenticación falla. Revisa sus logs:

docker compose logs --tail=50 guacd

Los mensajes indican si se trata de un error de red, de credenciales o de seguridad RDP (por ejemplo, NLA requerido o certificado no válido).

No puedes iniciar sesión tras una reinstalación. Si borraste el contenedor de PostgreSQL pero no el volumen pgdata, el script de initdb/ no se vuelve a ejecutar y siguen valiendo los usuarios anteriores. Para empezar de cero, elimina también el volumen con docker compose down -v (se pierden todos los datos).

Los logs muestran la IP del proxy en lugar de la del usuario. Confirma que REMOTE_IP_VALVE_ENABLED está a "true" y que Nginx envía X-Forwarded-For.

Conclusión

Tienes Apache Guacamole funcionando con Docker Compose y PostgreSQL, publicado por HTTPS con Nginx, protegido con doble factor y con conexiones SSH y RDP accesibles desde el navegador. Como siguientes pasos, activa la grabación de sesiones para auditar los accesos, integra la autenticación con LDAP, Active Directory u OpenID Connect para no gestionar usuarios a mano y cierra en los servidores de destino los puertos SSH y RDP a todo lo que no sea la IP de Guacamole.