Skip to main content

Apache Guacamole (RDP Access)

This guide explains how to open Remote Desktop Protocol (RDP) sessions from the browser using Apache Guacamole, utilizing VaultysHub for authentication. The user authenticates with their VaultysID, selects a server, and obtains the Windows desktop in a tab: they never see or enter the Windows account password.

How It Works​

Browser ──HTTPS──▶ oauth2-proxy ──▶ Guacamole (web) ──▶ guacd ──RDP──▶ Windows Server
│ │
└── VaultysID ◀── VaultysHub (OpenID Connect)
  • guacd opens the actual RDP session to the Windows server and relays it to the browser. Nothing is installed on the user's machine.
  • Guacamole (web application) stores the connections: host, port, domain, account, and password. These credentials remain server-side and are never sent to the browser.
  • VaultysHub authenticates the user. Guacamole receives their email address and grants access to the connections assigned to them.
  • oauth2-proxy is recommended as a front-end (see below). It manages the OpenID Connect connection with VaultysHub and passes the user's email to Guacamole.

Why oauth2-proxy?​

Guacamole's native OpenID module only handles the implicit flow. VaultysHub only allows it if the redirect URL is https:// and not localhost. oauth2-proxy uses the authorization code flow with PKCE: it works in all cases, and it is the recommended flow.

MethodOIDC FlowGuacamole in HTTPRecommended
oauth2-proxy + header authenticationAuthorization code + PKCEYes (testing only)Yes
Guacamole's native OpenID moduleImplicitNo, HTTPS requiredIf you cannot add oauth2-proxy

Prerequisites​

  • Docker and Docker Compose on a machine that can reach the Windows servers on TCP port 3389.
  • A DNS name and an HTTPS certificate for Guacamole in production (e.g., rdp.example.com behind your reverse proxy).
  • A Windows account per server or per environment, which Guacamole will use to open the session.
  • Administrator access to VaultysHub.

Configuration in VaultysHub​

1. Create the Application​

  1. Log in to VaultysHub as an administrator.
  2. Go to Applications → Add and create the application:
    • Name: Remote Desktop
    • URL: https://rdp.example.com/guacamole/
  3. In the application configuration, choose the type OIDC.

2. Set Up OpenID Connect​

  • Redirect URI: https://rdp.example.com/oauth2/callback
  • Allowed Scope: openid, email, and profile
  • Subject Identifier (sub): Email address
  • Use HTTP: disabled. This option is only for exposing VaultysHub's endpoints in http://.
  • Require PKCE: can be enabled, oauth2-proxy sends a PKCE S256.

Then note the Client ID (application identifier, noted as [appid] below) and the Client Secret.

Check the issuer announced by VaultysHub. This exact value must be provided to oauth2-proxy:

curl -s https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/.well-known/openid-configuration | grep -o '"issuer":"[^"]*"'

3. Grant Access to Users​

Assign the application to the relevant users or groups. A user who does not have access is denied by VaultysHub before reaching Guacamole.

Deploying Guacamole​

1. Initialize the Database​

mkdir -p guacamole/initdb guacamole/recordings && cd guacamole
docker run --rm guacamole/guacamole:1.6.0 /opt/guacamole/bin/initdb.sh --postgresql > initdb/initdb.sql

2. .env File​

OIDC_CLIENT_ID=[appid]
OIDC_CLIENT_SECRET=your-client-secret
# Exact value of the "issuer" field noted in the previous step
OIDC_ISSUER=https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]
# 32 random characters: openssl rand -hex 16
COOKIE_SECRET=replace-me
POSTGRES_PASSWORD=replace-me

3. docker-compose.yml File​

services:
guacd:
image: guacamole/guacd:1.6.0
restart: unless-stopped
volumes:
- ./recordings:/var/lib/guacamole/recordings

postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./initdb:/docker-entrypoint-initdb.d:ro
- ./pgdata:/var/lib/postgresql/data

guacamole:
image: guacamole/guacamole:1.6.0
restart: unless-stopped
depends_on: [guacd, postgres]
environment:
GUACD_HOSTNAME: guacd
POSTGRESQL_HOSTNAME: postgres
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USERNAME: guacamole
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
# Creates the Guacamole user on their first login via VaultysHub
POSTGRESQL_AUTO_CREATE_ACCOUNTS: "true"
# User passed by oauth2-proxy
HTTP_AUTH_HEADER: X-Forwarded-Email
RECORDING_SEARCH_PATH: /var/lib/guacamole/recordings
volumes:
- ./recordings:/var/lib/guacamole/recordings:ro
# No "ports": Guacamole should only be reachable by oauth2-proxy

oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.12.0
restart: unless-stopped
depends_on: [guacamole]
environment:
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_OIDC_ISSUER_URL: ${OIDC_ISSUER}
OAUTH2_PROXY_CLIENT_ID: ${OIDC_CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
OAUTH2_PROXY_REDIRECT_URL: https://rdp.example.com/oauth2/callback
OAUTH2_PROXY_SCOPE: openid email profile
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
OAUTH2_PROXY_UPSTREAMS: http://guacamole:8080/
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_PASS_USER_HEADERS: "true"
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"
OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
OAUTH2_PROXY_COOKIE_SECURE: "true"
ports:
- "127.0.0.1:4180:4180"

Then publish 127.0.0.1:4180 behind your HTTPS reverse proxy at https://rdp.example.com, and start:

docker compose up -d
Header Authentication

Guacamole trusts the X-Forwarded-Email header. Therefore, it should only be reachable by oauth2-proxy. Never expose its port 8080 directly: anyone could then send this header and impersonate another user.

4. First Administrator​

The base comes with a local account guacadmin / guacadmin. With the configuration above, the login page is no longer accessible, so:

  1. Log in for the first time via VaultysHub at https://rdp.example.com/guacamole/: your account is created, without rights.
  2. Grant it administrative rights from the database:
docker compose exec -T postgres psql -U guacamole -d guacamole_db -c "
INSERT INTO guacamole_system_permission (entity_id, permission)
SELECT entity_id, p::guacamole_system_permission_type
FROM guacamole_entity, unnest(ARRAY['ADMINISTER','CREATE_CONNECTION','CREATE_CONNECTION_GROUP','CREATE_USER','CREATE_USER_GROUP']) p
WHERE name = '[email protected]' AND type = 'USER'
ON CONFLICT DO NOTHING;"
  1. Reload the page. The Settings menu appears. Then delete the guacadmin account (Settings → Users).

Configure an RDP Connection​

In Settings → Connections → New Connection:

FieldValue
ProtocolRDP
Hostname / Portsrv01.corp.local / 3389
Username / PasswordThe Windows account used for this connection
Domain NameThe AD domain, e.g., CORP
Security ModeNLA (Network Level Authentication)
Ignore Server CertificateOnly if the server uses a self-signed certificate

Organize the servers by environment or domain with connection groups (New Group). Then, in Settings → Users (or Groups), check the connections each person is entitled to.

VaultysHub Groups

VaultysHub does not pass groups to the OIDC application. Therefore, rights are managed in Guacamole, by user or through Guacamole groups. VaultysHub decides who can enter, Guacamole decides which servers each can open.

Session Recording​

In the Screen Recording section of the connection, fill in:

  • Recording Path: ${HISTORY_PATH}/${HISTORY_UUID}
  • Automatically create a recording path: checked

Recordings are stored in ./recordings and can be replayed from Settings → History.

Testing the Configuration​

  1. Open https://rdp.example.com/guacamole/.
  2. You are redirected to VaultysHub: validate with your VaultysID.
  3. You arrive in Guacamole with the list of your connections.
  4. Open a connection: the Windows desktop appears without entering a password.
  5. Open the application from the VaultysHub dashboard: the opening appears in the audit log. A direct connection to the Guacamole URL also goes through VaultysHub, but is not logged as an application opening.

Alternative: Guacamole's Native OpenID Module​

If you cannot add oauth2-proxy, Guacamole can use VaultysHub directly. You must then serve Guacamole over HTTPS, on a name other than localhost.

In VaultysHub, declare the redirect URI https://rdp.example.com/guacamole/. Then, in the guacamole service, replace HTTP_AUTH_HEADER with:

OPENID_AUTHORIZATION_ENDPOINT: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/auth
OPENID_JWKS_ENDPOINT: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/jwks
OPENID_ISSUER: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]
OPENID_CLIENT_ID: "[appid]"
OPENID_REDIRECT_URI: https://rdp.example.com/guacamole/
OPENID_USERNAME_CLAIM_TYPE: email
OPENID_SCOPE: openid email profile

Then delete the oauth2-proxy service and publish Guacamole's port 8080 directly behind your HTTPS reverse proxy. The first administrator is created in the same way as in the First Administrator section.

Troubleshooting​

oauth2-proxy Restarts in a Loop: "issuer did not match"​

The value OIDC_ISSUER must be exactly the same as the issuer field from the OpenID configuration of VaultysHub (see the curl command above), including http or https. If the Use HTTP option is enabled on the application, the issuer starts with http://. Disable it unless VaultysHub itself is served over HTTP.

"Invalid redirect URI"​

The URI declared in VaultysHub must match exactly what is sent: https://rdp.example.com/oauth2/callback with oauth2-proxy, https://rdp.example.com/guacamole/ with the native module (including the trailing slash).

The User Arrives in Guacamole Without Any Connections​

This is normal on the first login: their Guacamole account has just been created. Assign them connections in Settings → Users.

The RDP Session Closes Immediately​

  • Check that guacd can reach the server: docker compose exec guacd nc -zv srv01.corp.local 3389.
  • Try the security mode Automatic Negotiation and check Ignore Server Certificate to isolate a certificate issue.
  • Check the logs: docker compose logs guacd.

Security​

  • HTTPS required in production. Keep OAUTH2_PROXY_COOKIE_SECURE: "true".
  • Guacamole never exposed directly when header authentication is active.
  • A dedicated and limited Windows account per environment. Avoid a domain admin account usable everywhere.
  • Session recording enabled for privileged accounts.
  • Regular updates of guacd and Guacamole, which include FreeRDP.

Resources​