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
sudoque pertenezca al grupodocker. - 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_ENABLEDhace que Guacamole registre la IP real del usuario (enviada por Nginx enX-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:
- Inicia sesión con
guacadmin/guacadmin. - Abre el menú de usuario (arriba a la derecha) y entra en Settings > Users > New User.
- Escribe tu nombre de usuario y una contraseña fuerte, marca todos los permisos de la sección Permissions (incluido Administer system) y guarda.
- Cierra sesión, entra con el nuevo usuario y, en Settings > Users, abre
guacadminy 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:
- Name: un nombre descriptivo, por ejemplo
web-01 (SSH). Protocol:SSH. - En Parameters > Network: Hostname con la IP privada o el nombre del servidor y Port
22. - 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.
- Guarda.
Para un servidor Windows por RDP:
- Protocol:
RDP. Hostname: la IP del servidor. Port:3389. - En Authentication: Username, Password y, si pertenece a un dominio, Domain.
- Security mode:
NLA (Network Level Authentication). Si el servidor usa un certificado autofirmado, marca Ignore server certificate. - 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.
