Skip to main content

Linux Images: GNOME Remote Desktop (Rocky 10+)

Last updated: August 1, 2026Latest Frontend Version: 2.17.4

Rocky Linux 10 and RHEL 10 have removed Xorg entirely. The XRDP/Xorg configuration used on Rocky 8/9 is not available on Rocky 10. Instead, use GNOME Remote Desktop (GRD) in system mode, which is Red Hat's supported RDP implementation for Wayland.

GRD system mode runs a single system-level RDP daemon on port 3389 that handles all incoming connections. When a user connects, GDM creates a headless GNOME session for them and GRD's handover mechanism redirects the RDP stream into that session, all through the same port. Multiple users can be logged in simultaneously, each with their own independent desktop.

The tiCrypt VM Controller provisions user accounts automatically at first login via a controller.toml hook. RDP connections go through a two-stage authentication: a shared RDP-level credential (NLA handshake), then the GDM login screen where users enter their tiCrypt-provisioned username and password.

Security limitation: no clipboard control

GNOME Remote Desktop has no administrator-facing clipboard restriction mechanism. Unlike XRDP's RestrictOutboundClipboard/RestrictInboundClipboard settings, GRD provides no way to block clipboard copy-out while allowing paste-in, or to disable clipboard sharing entirely through a supported configuration interface.

If your deployment requires clipboard control for data containment (e.g., secure-research VMs), use Rocky 8/9 with XRDP instead. See the compatibility matrix for a full comparison.

Compatibility Matrixโ€‹

CapabilityXRDP (Rocky 8/9)GRD (Rocky 10+)
Desktop environmentXfce / MATE (lightweight)GNOME (heavier, ~2 vCPU / 2 GB RAM per session minimum)
Clipboard direction controlFull (RestrictOutboundClipboard / RestrictInboundClipboard)Not available
Session reapingBuilt-in (MaxSessions, KillDisconnected, DisconnectedTimeLimit)Manual (via loginctl)
Session reconnectAll versionsRocky 10+ (verify on your build)
Multi-user modelTerminal-server (many concurrent sessions)Native multi-user via GDM session routing; one GNOME session per user
AuthenticationSingle login promptGDM login screen (PAM-backed)

1. Install Packagesโ€‹

dnf install -y gdm gnome-remote-desktop
PackagePurpose
gdmDisplay manager. Handles user authentication and headless session creation for each RDP connection. The system-level GRD daemon calls GDM's D-Bus interface to spin up per-user sessions on demand
gnome-remote-desktopRDP server (Wayland-native, uses Mutter + PipeWire). Provides the system daemon, per-session handover daemon, and the grdctl CLI
note

The gdm package pulls in gnome-session, gnome-shell, gnome-settings-daemon, and other GNOME components as RPM dependencies. The full "Server with GUI" environment group is not required.

Set the system boot target to graphical so GDM starts automatically:

systemctl set-default graphical.target
danger

systemctl set-default graphical.target is mandatory. gdm.service is WantedBy=graphical.target, and the system-level GRD daemon depends on GDM's D-Bus interface to create sessions. Without this, neither GDM nor the RDP daemon starts at boot.

Reboot after installation:

reboot

2. Generate TLS Certificatesโ€‹

GRD requires a TLS certificate and key for the RDP connection. Generate a certificate at image build time:

mkdir -p /etc/pki/ticrypt/grd
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
-subj "/CN=tiCrypt-VM-RDP" \
-keyout /etc/pki/ticrypt/grd/tls.key \
-out /etc/pki/ticrypt/grd/tls.crt
chown gnome-remote-desktop:gnome-remote-desktop /etc/pki/ticrypt/grd/tls.key /etc/pki/ticrypt/grd/tls.crt
chmod 600 /etc/pki/ticrypt/grd/tls.key
chmod 644 /etc/pki/ticrypt/grd/tls.crt

Alternatively, if freerdp is installed (dnf install -y freerdp), you can use winpr-makecert:

winpr-makecert -silent -rdp -path /etc/pki/ticrypt/grd tls
chown gnome-remote-desktop:gnome-remote-desktop /etc/pki/ticrypt/grd/tls.key /etc/pki/ticrypt/grd/tls.crt
chmod 600 /etc/pki/ticrypt/grd/tls.key
chmod 644 /etc/pki/ticrypt/grd/tls.crt
note

These are self-signed certificates for the RDP transport layer. RDP clients will show a certificate warning on first connection. tiCrypt's tunnel infrastructure terminates the external TLS connection separately, so these certificates are only used for the internal RDP session between the tunnel endpoint and the GRD daemon.

The private key is owned by the gnome-remote-desktop system user (created by the gnome-remote-desktop package) because the system daemon runs under that account.

3. Configure System RDPโ€‹

Configure the system-level GRD daemon to accept RDP connections. These commands write to the system daemon's GSettings profile, not to any individual user's settings.

grdctl --system rdp set-tls-cert /etc/pki/ticrypt/grd/tls.crt
grdctl --system rdp set-tls-key /etc/pki/ticrypt/grd/tls.key
grdctl --system rdp set-credentials ticrypt ticrypt
grdctl --system rdp enable
Two-stage RDP authentication

GRD system mode uses two authentication stages:

  1. RDP-level credential (NLA/CredSSP): The shared credential set by set-credentials above. The RDP client must supply this to complete the initial NLA handshake before reaching the GDM login screen. Without this step, grdctl --system rdp enable will fail with a "Credentials not set" error, or clients will see "Could not find user in SAM database."

  2. GDM login (PAM): After the NLA handshake succeeds, the user sees the GDM login screen and authenticates with their Linux system password (set by the VM Controller and available via "Copy Password" in the tiCrypt frontend).

The shared RDP credential (ticrypt/ticrypt) is baked into the image and is the same for all VMs built from it. Since tiCrypt's tunnel provides the real perimeter authentication, this credential is a protocol-level gate, not a security boundary. Change it to a value appropriate for your organization.

note

grdctl --system targets the system-level daemon (gnome-remote-desktop.service), which is distinct from the per-user headless daemon (gnome-remote-desktop-headless.service). System mode is what provides native multi-user support through a single port.

How system mode multi-user routing works
  1. The system-level GRD daemon (gnome-remote-desktop.service) binds port 3389 and accepts incoming RDP connections.

  2. On a new connection, the daemon calls GDM's org.gnome.DisplayManager.RemoteDisplayFactory.CreateRemoteDisplay D-Bus method, which creates a new headless GDM login session tagged with a per-client remote-id.

  3. That headless GDM session runs gnome-remote-desktop-handover.service, which registers a D-Bus handover interface with the system daemon.

  4. The system daemon uses an RDP Server Redirection PDU to redirect the client (with a one-time routing token) back through port 3389 to the handover session. The client reconnects automatically; most RDP clients handle this transparently.

  5. The user sees the GDM login screen in their RDP client and authenticates with their system credentials.

  6. After authentication, GDM transitions from the login session to the user's actual GNOME session, and the handover mechanism redirects the RDP stream into the user's desktop.

All connections flow through port 3389. The system daemon, GDM, and the handover service coordinate via D-Bus and routing tokens so that each user ends up in their own independent GNOME session.

4. Create Provisioning Scriptโ€‹

Since GRD system mode handles session creation and RDP routing automatically via GDM, the provisioning script is minimal. The VM Controller already creates user accounts with random passwords (configurable via passwordLength in controller.toml), and users retrieve their password from the Copy Password button in the tiCrypt frontend.

Create /usr/local/bin/ticrypt-grd-provision.sh:

#!/bin/bash
set -euo pipefail

USER_NAME="$1"

# Keep the user's systemd user-manager instance running for background
# services (e.g., dbus-broker, pipewire) even when no login session is
# active. This does not control GRD session persistence (GDM handles that),
# but ensures the user's service manager is available immediately when
# GDM creates their next session.
loginctl enable-linger "$USER_NAME"

Make the script executable:

chmod +x /usr/local/bin/ticrypt-grd-provision.sh
GDM login password

At the GDM login screen, users enter their Linux system password, which the VM Controller sets when creating their account. Users can copy this password from the tiCrypt frontend using the Copy Password button, the same flow used with XRDP.

note

Unlike the per-user headless mode (grdctl --headless), system mode requires no per-user GRD configuration. The system daemon handles TLS, port binding, and session routing for all users.

5. Configure controller.tomlโ€‹

Add the following to your image's controller.toml. This configures RDP port tunneling and the provisioning hook that prepares each user for GDM login.

[tunnel]
allowedPorts = 3389

[tunnel.services]
rdp = 3389

[commands.rootCommands.runOnlyOnceCommands]
on_login = {grdProvision = "/usr/local/bin/ticrypt-grd-provision.sh @:user:"}
SettingPurpose
allowedPorts = 3389Permits RDP traffic through the tiCrypt tunnel
rdp = 3389Labels the port in the tiCrypt frontend connection instructions
on_loginFires when a user first connects through the tiCrypt frontend
runOnlyOnceCommandsEnsures the provisioning script runs once per user. If the command fails, it retries on subsequent logins until it succeeds
@:user:Replaced at runtime with the tiCrypt-provisioned username
tip

If your controller.toml already has a [commands.rootCommands.runOnlyOnceCommands] section with an existing on_login entry, add grdProvision to the same line:

on_login = {existingCommand = "...", grdProvision = "/usr/local/bin/ticrypt-grd-provision.sh @:user:"}

Multiple commands in the same event run in the order they appear.

6. Session Managementโ€‹

Each user gets their own GNOME desktop session, created by GDM when they authenticate. Multiple users can be logged in simultaneously, each with an independent session routed through the single system daemon on port 3389.

Session persistence (reconnect): GNOME 47 (shipped with Rocky 10) supports persistent headless sessions. A disconnected user can reconnect and resume their existing session with the same open windows and running processes. Verify this on your build by disconnecting and reconnecting an RDP session.

caution

Session persistence was introduced in GNOME 47. Earlier GNOME versions treated disconnect as logout. If your Rocky 10 build ships a different GNOME version, test reconnect behavior before relying on it (gnome-shell --version to check).

Same-account reconnect

Behavior when a second RDP connection arrives for an already-connected user is not fully documented upstream. It may resume the existing session, present a confirmation dialog, or displace the first connection depending on the GNOME version and whether the first connection is still active. Test this scenario on your build to confirm the expected behavior.

Session reaping: Disconnected sessions can accumulate at the GNOME-session level. Use loginctl to monitor and terminate stale sessions:

loginctl list-sessions
loginctl terminate-session <session-id>
Automated session reaper timer (optional)

Create /etc/systemd/system/ticrypt-session-reaper.service:

[Unit]
Description=Reap GNOME headless sessions idle for more than 8 hours

[Service]
Type=oneshot
ExecStart=/bin/bash -c '\
MAX_IDLE=28800; \
loginctl list-sessions --no-legend | while read sid uid user seat tty; do \
idle=$(loginctl show-session "$sid" -p IdleHint --value); \
if [ "$idle" = "yes" ]; then \
since=$(loginctl show-session "$sid" -p IdleSinceHintMonotonic --value); \
mono=$(cut -d" " -f1 /proc/uptime | cut -d"." -f1); \
idle_secs=$((mono - since / 1000000)); \
if [ "$idle_secs" -ge "$MAX_IDLE" ]; then \
loginctl terminate-session "$sid"; \
fi; \
fi; \
done'

Create /etc/systemd/system/ticrypt-session-reaper.timer:

[Unit]
Description=Run session reaper every hour

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

Enable:

systemctl enable --now ticrypt-session-reaper.timer
Idle vs. disconnected

Unlike XRDP's DisconnectedTimeLimit (which only counts time since actual disconnect), this reaper uses IdleHint from loginctl, which reflects keyboard/mouse input inactivity. A user who is still connected but has not interacted with the desktop for 8+ hours (e.g., watching a long-running computation) would also be terminated. Adjust MAX_IDLE or the reaping criteria to match your workload.

note

Do not add I (IP) to any session-matching logic. As with XRDP, tiCrypt traffic arrives through the tunnel infrastructure, so the source IP seen by GRD is always the tunnel endpoint, not the user's real IP.

7. Clipboard Restrictionsโ€‹

No clipboard control available

GNOME Remote Desktop provides no supported mechanism to restrict clipboard sharing.

  • grdctl has no clipboard subcommand
  • The GSettings schema (org.gnome.desktop.remote-desktop.rdp) has no clipboard key
  • A private D-Bus interface (org.gnome.Mutter.RemoteDesktop.Session.EnableClipboard/DisableClipboard) exists but is explicitly documented as internal to libmutter with no compatibility guarantee between versions

For secure-research VMs that require clipboard control, use Rocky 8/9 with XRDP instead.

If you must use Rocky 10, consider compensating controls:

  • Network-level data loss prevention at the tiCrypt infrastructure layer
  • Application-level clipboard restrictions within the VM
  • Policy-based controls enforced through the desktop environment
  • Patching gnome-remote-desktop or freerdp-libs to disable the cliprdr RDP channel at the protocol level (requires maintaining a custom RPM across updates)

8. Fix Startup Orderingโ€‹

Ensure GDM starts reliably before the GRD system daemon that depends on it. Create a systemd drop-in override:

mkdir -p /etc/systemd/system/gdm.service.d
cat > /etc/systemd/system/gdm.service.d/override.conf <<'EOF'
[Unit]
After=dbus.service network-online.target
Wants=dbus.service network-online.target

[Service]
Restart=on-failure
RestartSec=2
StartLimitIntervalSec=60
StartLimitBurst=10
EOF

systemctl daemon-reload
danger

The Restart=on-failure directive is critical. Without it, if GDM encounters an error during startup (before D-Bus is fully ready), systemd will not restart it. The GRD system daemon depends on GDM's D-Bus interface to create sessions; a failed GDM means no RDP.

9. Disable NSS Caching Daemonsโ€‹

systemctl disable --now sssd nscd 2>/dev/null
note

Identical reasoning to XRDP: sssd and nscd cache name-service lookups, including negative results. If the provisioning script triggers a user lookup before the VM Controller has finished creating that account, the "user not found" response is cached. On an isolated VLAN with no AD or LDAP, these services provide no benefit. Disable them so NSS lookups resolve directly from /etc/passwd.

10. Disable Idle Suspendโ€‹

GNOME's default power settings suspend the machine after approximately 15 minutes of inactivity, even on AC power. On a headless multi-user VM, this would terminate every active RDP session simultaneously.

Disable idle suspend at image build time with a system-wide dconf override:

mkdir -p /etc/dconf/db/local.d
cat > /etc/dconf/db/local.d/00-no-idle-suspend <<'EOF'
[org/gnome/settings-daemon/plugins/power]
sleep-inactive-ac-timeout=0
sleep-inactive-ac-type='nothing'
sleep-inactive-battery-timeout=0
sleep-inactive-battery-type='nothing'
EOF

mkdir -p /etc/dconf/db/local.d/locks
cat > /etc/dconf/db/local.d/locks/00-no-idle-suspend <<'EOF'
/org/gnome/settings-daemon/plugins/power/sleep-inactive-ac-timeout
/org/gnome/settings-daemon/plugins/power/sleep-inactive-ac-type
/org/gnome/settings-daemon/plugins/power/sleep-inactive-battery-timeout
/org/gnome/settings-daemon/plugins/power/sleep-inactive-battery-type
EOF

dconf update
danger

The lock file prevents individual users from re-enabling suspend through GNOME Settings. Without this override, an idle VM will suspend and kill all active RDP sessions with no warning.

11. Enable Servicesโ€‹

systemctl enable gdm.service
systemctl enable gnome-remote-desktop.service
ServicePurpose
gdm.serviceDisplay manager. Creates headless GNOME sessions for each RDP user on demand
gnome-remote-desktop.serviceSystem-level RDP daemon. Binds port 3389, accepts connections, and coordinates with GDM for session creation and handover
note

Per-user gnome-remote-desktop-handover.service instances are started automatically by GDM as part of each login session. They do not need to be enabled manually.

12. SELinuxโ€‹

System mode SELinux policy

GRD's system mode on Rocky 10 may have incomplete SELinux policy coverage. Red Hat's own documentation notes that headless remote desktop may require permissive mode as a prerequisite.

After enabling services, check for denials:

ausearch -m avc -ts recent | grep -E 'gnome-remote|gdm|mutter'

If denials are present and blocking functionality:

  1. For testing, temporarily set permissive mode: setenforce 0
  2. For production, generate a custom policy module from the collected denials: audit2allow -a -M ticrypt-grd && semodule -i ticrypt-grd.pp

Do not leave permissive mode enabled in production without evaluating the security implications.

13. Verifyโ€‹

After building the image, verify the base configuration:

systemctl get-default
systemctl is-enabled gdm.service
systemctl is-enabled gnome-remote-desktop.service
grdctl --system status
ls -la /etc/pki/ticrypt/grd/tls.{crt,key}
ls -la /usr/local/bin/ticrypt-grd-provision.sh

The default target should be graphical.target, both services should be enabled, grdctl --system status should report RDP as enabled with valid TLS certificate paths, and the provisioning script should be executable.

After deploying a VM and logging in through tiCrypt, verify the system is accepting RDP:

systemctl status gdm.service
systemctl status gnome-remote-desktop.service
ss -tlnp | grep 3389
loginctl list-sessions

GDM and the GRD system daemon should be active, a process should be listening on port 3389, and after an RDP user has logged in, loginctl should show their session.

RDP client configuration

When connecting via RDP, users go through two prompts:

  1. RDP credential prompt: The RDP client asks for a username and password before connecting. Enter the shared system credential configured with grdctl --system rdp set-credentials (default in this guide: ticrypt / ticrypt). This completes the NLA handshake.
  2. GDM login screen: After the NLA handshake, the GDM login screen appears inside the RDP session. Enter the tiCrypt-provisioned username and the password from the Copy Password button in the tiCrypt frontend.

Windows' built-in mstsc client may require an additional setting for GRD's session redirection to work correctly. In the .rdp file or connection settings, set:

use redirection server name:i:1

FreeRDP-based clients (xfreerdp, Remmina, GNOME Connections) do not need this setting.