Linux Images: GNOME Remote Desktop (Rocky 10+)
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.
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โ
| Capability | XRDP (Rocky 8/9) | GRD (Rocky 10+) |
|---|---|---|
| Desktop environment | Xfce / MATE (lightweight) | GNOME (heavier, ~2 vCPU / 2 GB RAM per session minimum) |
| Clipboard direction control | Full (RestrictOutboundClipboard / RestrictInboundClipboard) | Not available |
| Session reaping | Built-in (MaxSessions, KillDisconnected, DisconnectedTimeLimit) | Manual (via loginctl) |
| Session reconnect | All versions | Rocky 10+ (verify on your build) |
| Multi-user model | Terminal-server (many concurrent sessions) | Native multi-user via GDM session routing; one GNOME session per user |
| Authentication | Single login prompt | GDM login screen (PAM-backed) |
1. Install Packagesโ
dnf install -y gdm gnome-remote-desktop
| Package | Purpose |
|---|---|
gdm | Display 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-desktop | RDP server (Wayland-native, uses Mutter + PipeWire). Provides the system daemon, per-session handover daemon, and the grdctl CLI |
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
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
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
GRD system mode uses two authentication stages:
-
RDP-level credential (NLA/CredSSP): The shared credential set by
set-credentialsabove. The RDP client must supply this to complete the initial NLA handshake before reaching the GDM login screen. Without this step,grdctl --system rdp enablewill fail with a "Credentials not set" error, or clients will see "Could not find user in SAM database." -
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.
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
-
The system-level GRD daemon (
gnome-remote-desktop.service) binds port 3389 and accepts incoming RDP connections. -
On a new connection, the daemon calls GDM's
org.gnome.DisplayManager.RemoteDisplayFactory.CreateRemoteDisplayD-Bus method, which creates a new headless GDM login session tagged with a per-clientremote-id. -
That headless GDM session runs
gnome-remote-desktop-handover.service, which registers a D-Bus handover interface with the system daemon. -
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.
-
The user sees the GDM login screen in their RDP client and authenticates with their system credentials.
-
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
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.
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:"}
| Setting | Purpose |
|---|---|
allowedPorts = 3389 | Permits RDP traffic through the tiCrypt tunnel |
rdp = 3389 | Labels the port in the tiCrypt frontend connection instructions |
on_login | Fires when a user first connects through the tiCrypt frontend |
runOnlyOnceCommands | Ensures 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 |
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.
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).
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
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.
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โ
GNOME Remote Desktop provides no supported mechanism to restrict clipboard sharing.
grdctlhas 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-desktoporfreerdp-libsto disable thecliprdrRDP 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
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
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
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
| Service | Purpose |
|---|---|
gdm.service | Display manager. Creates headless GNOME sessions for each RDP user on demand |
gnome-remote-desktop.service | System-level RDP daemon. Binds port 3389, accepts connections, and coordinates with GDM for session creation and handover |
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โ
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:
- For testing, temporarily set permissive mode:
setenforce 0 - 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.
When connecting via RDP, users go through two prompts:
- 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. - 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.