
Open a Windows desktop, access a Linux machine over SSH, and work on a VNC session using only the browser: that is the proposal of Apache Guacamole. It centralizes remote access in a web portal, without requiring a specific client on the computer of whoever is accessing.
In this guide, we will understand the architecture, set up a lab with Docker Compose and PostgreSQL, and discuss local authentication, LDAP/Active Directory, SSO, MFA, security, and operations. The focus is to build a comprehensible foundation before putting an administrative gateway on the network.
What is Apache Guacamole — and what it isn't
The Apache Guacamole is a remote access gateway clientless, with support for protocols such as RDP, VNC, and SSH. “Clientless” means the user uses a modern browser: on the server, there are still components, configuration, and dependencies. The project is open source under Apache License 2.0.
It does not create virtual machines, does not install a desktop on the server, and does not turn any machine into an accessible destination. The remote service must already exist, be configured, and be reachable by the gateway. On a Linux system without a graphical environment, SSH remains a terminal, not a magic desktop inside the page.
This differentiates it from phpVirtualBox: that panel manages VirtualBox VMs; Guacamole provides access to remote sessions. These are tools that can address complementary needs.
Which version to use as a reference
In the consultation conducted on 14 September 2026, the site and the official releases file showed Guacamole 1.6.0, released on 22 June 2025, as the current version. The tutorial pins this version in both project images; it does not use latest.
The release notes for 1.6.0 highlight rendering improvements, Docker support, and bulk connection import, among other changes. Before repeating the procedure in the future, also consult the security advisories and the documentation corresponding to the chosen version.
The web application has a Java backend and delivers the JavaScript client to the browser. Session traffic uses the Guacamole protocol; the guacd and its protocol components bridge to the remote targets. It is not the browser speaking RDP directly to Windows. Reference: official architecture.
Navegador
↓ HTTPS / WebSocket (ou túnel HTTP)
Proxy reverso → aplicação Guacamole (Java/Tomcat)
├── banco: usuários, permissões e conexões
├── LDAP ou provedor de identidade, se configurado
↓ protocolo Guacamole
guacd
├── RDP → Windows ou servidor RDP
├── SSH → Linux/Unix
└── VNC → servidor VNC
My design recommendation is to separate two questions: who can enter the portal and which targets the gateway can reach. The firewall should restrict the second independently of the first. An authenticated portal does not justify opening the entire internal network to the connection process.
The database does not transport the desktop image: it stores administrative data according to the extension used. The introduction documentation also helps distinguish the ready-made application from the APIs available for custom integrations.
When it makes sense to use it
- Linux and Windows labs accessed from different computers.
- Teams that need a central point for administrative connections.
- Training environments with targets and permissions defined per user.
- Remote support for systems already reachable by the gateway infrastructure.
The expected gain is reducing the variety of clients at the endpoint and organizing access. This does not eliminate the need for a credential policy, monitoring, and segmentation. There is also no universal number of sessions per server: resolution, protocol, graphical activity, and latency change consumption. Measure your scenario before sizing production.
Docker lab: scope and prerequisites
You need Docker Engine, Docker Compose, OpenSSL, and access to the image registry. The example uses three services: PostgreSQL, guacd and the Guacamole application. Only the portal is published, and only on the host loopback. Database and port 4822 receive no external publishing. Refer to the official Docker installation.
Validation performed: the Compose below was started locally; the database initialized, the portal responded with HTTP 200 and local login with the PostgreSQL backend worked. The application's SQL user was checked without superuser privileges. RDP, VNC, LDAP/AD, SSO, MFA, and production TLS were not validated against real services in this lab; these sections are configuration guidance based on the manual.
Create a new folder. The init scripts described here are for an empty database, not for migrating an existing installation:
mkdir -p guacamole-lab/init
cd guacamole-lab
umask 077
printf 'DB_ADMIN_PASSWORD=%s\nGUAC_DB_PASSWORD=%s\n' \
"$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > .env
printf '.env\n' > .gitignore
chmod 600 .env
Passwords are generated locally. Do not publish the .env. Users with administrative access to Docker can inspect container variables; file permissions do not turn environment variables into a vault of secrets.
1. Creating the Compose
Save as compose.yaml:
name: lp-guacamole-lab
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DB_ADMIN_PASSWORD:?defina DB_ADMIN_PASSWORD}
GUAC_DB_PASSWORD: ${GUAC_DB_PASSWORD:?defina GUAC_DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: [CMD-SHELL, 'pg_isready -h 127.0.0.1 -U postgres -d guacamole_db']
interval: 5s
timeout: 3s
retries: 20
networks: [backend]
guacd:
image: guacamole/guacd:1.6.0
restart: unless-stopped
networks: [backend]
guacamole:
image: guacamole/guacamole:1.6.0
restart: unless-stopped
depends_on:
db:
condition: service_healthy
guacd:
condition: service_started
environment:
GUACD_HOSTNAME: guacd
POSTGRESQL_ENABLED: 'true'
POSTGRESQL_HOSTNAME: db
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USERNAME: guacamole_app
POSTGRESQL_PASSWORD: ${GUAC_DB_PASSWORD:?defina GUAC_DB_PASSWORD}
ports:
- '127.0.0.1:18080:8080'
networks: [backend]
volumes:
pgdata:
networks:
backend:
The application uses guacamole_app, not the PostgreSQL administrative account. The volume pgdata preserves the database when containers are recreated. The Docker network does not publish internal services, but still allows outbound connectivity; restricting to authorized destinations must be planned at the firewall.
The image postgres:17 pins the major line, not an immutable digest. For a reproducible deployment, record the tested digests and plan updates. Do not switch the PostgreSQL major version just by changing the tag on the same volume.
2. Initializing the schema and SQL user
Guacamole provides the bootstrap SQL inside its image. Generate the file before starting the database:
docker pull guacamole/guacamole:1.6.0
docker run --rm guacamole/guacamole:1.6.0 \
/opt/guacamole/bin/initdb.sh --postgresql > init/001-schema.sql
test -s init/001-schema.sql
Save the content below as init/002-app-user.sh. It creates the application's SQL account and grants access to the required data, without granting server administration:
#!/bin/sh
set -eu
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \
-v app_password="$GUAC_DB_PASSWORD" <<'SQL'
CREATE USER guacamole_app WITH PASSWORD :'app_password';
GRANT CONNECT ON DATABASE guacamole_db TO guacamole_app;
GRANT USAGE ON SCHEMA public TO guacamole_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO guacamole_app;
GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA public TO guacamole_app;
SQL
chmod 644 init/001-schema.sql init/002-app-user.sh
Schema preparation and permissions are described in the PostgreSQL manual. The files will be processed on the first initialization of the empty volume. Editing the .env afterward does not automatically rotate passwords already stored in the database.
3. Starting up and testing the portal
docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=100 db guacd guacamole
curl -I http://127.0.0.1:18080/guacamole/
Wait for Tomcat to initialize. The local address is http://127.0.0.1:18080/guacamole/. If Docker is on another server, you can use a temporary SSH tunnel, run on your computer:
ssh -N -L 18080:127.0.0.1:18080 usuario@servidor
Replace user and server with your own environment. Then open the same local address in the browser. This keeps the test off public exposure.
The initial schema creates the access guacadmin / guacadmin. Only log in through the restricted access, immediately change the password, and prepare an individual administrative account before releasing users. Do not confuse this portal login with the SQL passwords from .env. Reference: database authentication.
4. Setting up the first connection
In the administrative area, create a connection and choose the appropriate protocol, address, and credentials. Start with a disposable lab destination and grant access only to the test account. Verify connectivity from the network of the guacd, not just from your laptop.
| Protocol | Required destination | Essential verification |
|---|---|---|
| SSH | Configured SSH server | User, key or password, and host identity. |
| RDP | RDP server enabled | Security policy, certificate, account, and domain where applicable. |
| VNC | VNC server available | Authentication, transport protection, and port configured on the destination. |
Features such as audio, file transfer, and clipboard depend on the protocol and configuration. Don't enable all of them for convenience: choose what the user needs. Avoid turning “ignore certificate” into a permanent RDP setting. The parameters are in the configuration guide; for key-based access, see also our article on SSH authentication.
HTTPS and reverse proxy: don't expose the lab as-is
For shared use, place the portal behind HTTPS. The proxy must preserve WebSocket and not accumulate tunnel data in buffers. The example below is a location block for an nginx server with TLS already configured, running on the same host as Docker; it is not a complete TLS configuration:
location /guacamole/ {
proxy_pass http://127.0.0.1:18080;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
access_log off;
}
The access log for this location has been disabled so that tokens present in URLs are not recorded. If you need it, set a sanitized format without sensitive parameters. Before enabling, validate your configuration with nginx -t. If nginx is in another container, the loopback above does not represent the host: adapt the network and upstream.
To correctly register the origin, the manual instructs you to enable REMOTE_IP_VALVE_ENABLED and limit trusted proxies with PROXY_ALLOWED_IPS_REGEX. Do not accept origin headers from any client. Proxy and TLS reference.
Authentication: three different questions
- Who is logging in? Local login, LDAP directory, or SSO provider.
- What can this person access? Permissions on connections, groups, and administration.
- How does the session reach the destination? Credentials accepted by Windows, Linux, or VNC server.
Do not treat the three steps as a single password. An identity authenticated in the portal does not automatically receive an account on the remote system. This separation also helps investigate the classic case: the login works, but no connection appears.
LDAP and Active Directory: centralized identity
The LDAP extension authenticates by bind. There are two designs: store connections in the directory with the schema guacConfigGroup, or use LDAP for identity and a database for connections. The second avoids changing the schema just to store connections. The association between accounts and groups depends on matching names between sources. LDAP/AD documentation.
Illustrative example for AD, to add to the environment from guacamole, keeping PostgreSQL:
LDAP_ENABLED: "true"
LDAP_HOSTNAME: "dc01.example.org"
LDAP_PORT: "636"
LDAP_ENCRYPTION_METHOD: "ssl"
LDAP_USER_BASE_DN: "DC=example,DC=org"
LDAP_USERNAME_ATTRIBUTE: "sAMAccountName"
LDAP_SEARCH_BIND_DN: "CN=svc-guacamole,OU=Servicos,DC=example,DC=org"
LDAP_SEARCH_BIND_PASSWORD: ${LDAP_BIND_PASSWORD:?defina LDAP_BIND_PASSWORD}
Replace the values with the real directory. The search account resolves the user's DN; it does not need to be a domain administrator. LDAPS uses ssl; StartTLS uses starttls. . The certificate chain must be trusted by Java. Do not disable validation to hide a certificate error.
Configuring LDAP does not automatically eliminate local database login: the extensions can coexist. Review local accounts to avoid an unwanted alternative path after blocking someone in AD.
My homologation checklist is to test authorized user, user without permission, invalid credential, locked account, invalid certificate, and directory unavailability. For groups, check bases and attributes before associating permissions. Do not assume that every AD group automatically became an authorized group in the portal.
SSO: OpenID Connect, SAML, and CAS
The Guacamole offers single sign-on options to delegate authentication. Unlike the LDAP form, SSO can redirect the user to an identity provider shared with other applications. The manual also covers certificates and smart cards.
| Option | What to evaluate |
|---|---|
| OpenID Connect | Supported flow, issuer, public keys, client, and redirect URI. |
| SAML | Metadata, provider identity, certificates, and user identification. |
| CAS | Existing CAS server, service URL, and validation configuration. |
In the documentation 1.6.0, the extension OpenID Connect uses the implicit. flow. Do not assume support for Authorization Code with PKCE just because the provider offers that flow. Check compatibility and the organization's security policy before choosing the integration. The extension handles authentication; connection data needs to come from another extension, such as a database extension.
For environments already standardized on these protocols, consult the specific configurations in SAML and CAS. Do not copy endpoints between protocols: metadata, validations, and responses are different.
My deployment playbook starts with a test identity and a stable identifier, combined with minimum permissions. Then come rejection, expiration, logout, and administrative recovery cases. Also test what happens to an already open session after revoking the user: blocking new logins does not prove instant termination of all sessions.
MFA: TOTP on the portal or policy on the provider
The TOTP extension adds a second factor and requires storing enrollment data; PostgreSQL can fulfill this role. In Docker, enabling it uses TOTP_ENABLED: "true". The user completes enrollment with their authenticator.
Enable it first with a test account and prepare a recovery procedure. If SSO requires MFA on the provider, validate the policy applied to the Guacamole application; do not infer protection merely because a corporate provider exists. Avoid stacking challenges unnecessarily and review user and group exceptions.
Recording and auditing require planning
Guacamole offers session recording and browser playback through configuration. The playback extension locates the files related to the history; the database alone does not necessarily contain the recording. Recording and playback guide.
The Compose in this article does not enable recordings. If you add them, plan volumes, permissions, space, retention, and access to the player. Sessions can expose sensitive data: inform users, restrict who reviews the material, and do not enable keystroke capture without an explicit need.
Backup, update, and diagnostics
Back up the database, configurations, custom extensions, and additional persistent files. To produce a logical dump from the lab:
umask 077
docker compose exec -T db pg_dump -U postgres -d guacamole_db -Fc \
> "guacamole-$(date +%F-%H%M%S).dump"
Save the protected dump and test restoration in another environment. Recreating the container is not equivalent to restoring data. Do not run docker compose down -v in production: the option removes associated volumes and can wipe the database.
During the update, read the applicable release notes and migration scripts, preserve extension compatibility, and schedule downtime. If the migration creates tables or sequences, reapply the necessary permissions to the application's SQL account. Restarting the application may interrupt sessions. Our guide on Uptime Kuma with Docker and nginx helps complement the portal's monitoring.
| Symptom | Where to start |
|---|---|
| Portal does not open | Application logs, local port, Tomcat startup, and proxy. |
| Local login fails | Initialized schema, SQL user, password, and permissions. |
| Login works; there are no connections | Permissions and identity association between sources. |
| Remote connection fails | guacd logs, DNS, route, firewall and service on the target. |
| LDAP fails | Base DN, user attribute, bind, and Java TLS trust. |
| SSO redirects back to error or loops | Public URI, provider identification, mapping, and proxy headers. |
| Session freezes or becomes slow | WebSocket, buffering, timeouts, latency, and real load. |
Checklist before making it available to the team
- Initial password replaced and individual administrative accounts.
- HTTPS and trusted proxies configured; access to guacd restricted.
- Limited destinations and permissions, including in the firewall.
- Authentication, MFA, and approved recovery paths.
- Credentials, dumps, and logs protected against disclosure.
- Backups restored in testing and an update routine defined.
- Remote integrations tested with low-privilege accounts.
Apache Guacamole can make remote access more organized, but it also concentrates access to important systems. Start with the lab, understand each layer, and only then expand usage. The permanent reference is the official manual: installing the portal is only the beginning of secure operations.