Skip to main content

Linux Images: XRDP Configuration

Last updated: August 20, 2026Latest Frontend Version: 2.17.7

Enable XRDP if users need a graphical desktop session via RDP. XRDP provides the RDP server, and users connect through tiCrypt's TLS tunnel infrastructure.

caution

GNOME on RHEL 8+ defaults to Wayland, which is incompatible with XRDP's Xorg backend. Use Xfce or MATE as the desktop environment for reliable RDP sessions.

1. Install Packagesโ€‹

XRDP is in the EPEL repository:

dnf install -y epel-release
dnf install -y xrdp xorgxrdp
dnf groupinstall -y "Xfce" --with-optional
Backend choice

The xorgxrdp package provides a native Xorg backend for XRDP, giving the best performance and clipboard/drive redirection support. The alternative is Xvnc (via tigervnc-server), which is more forgiving of Xorg version mismatches but has higher latency and limited channel support. Use xorgxrdp unless you encounter Xorg compatibility issues after a kernel or mesa update.

2. Configure the Session Managerโ€‹

Replace the contents of /etc/xrdp/startwm.sh with the following. This preserves user profile sourcing (/etc/profile, ~/.profile) so environment variables, PATH modifications, and module loads are available in the desktop session:

#!/bin/sh

if [ -r /etc/profile ]; then
. /etc/profile
fi
if [ -r ~/.bash_profile ]; then
. ~/.bash_profile
elif [ -r ~/.bash_login ]; then
. ~/.bash_login
elif [ -r ~/.profile ]; then
. ~/.profile
fi

unset DBUS_SESSION_BUS_ADDRESS
unset XDG_RUNTIME_DIR

exec startxfce4
Why replace the default startwm.sh?

The unset DBUS_SESSION_BUS_ADDRESS and unset XDG_RUNTIME_DIR lines are the critical part. When xrdp-sesman forks a session, it can pass its own stale values for these variables into the user's desktop. This silently breaks PolicyKit prompts, file manager operations, and audio -- even when pam_systemd.so is correctly configured in step 3. The exec startxfce4 bypasses the default session-detection logic and launches Xfce directly.

3. Configure PAM for Session Registrationโ€‹

The default /etc/pam.d/xrdp-sesman on RHEL does not include pam_systemd.so. Without it, systemd-logind does not register the RDP session, /run/user/<uid> is not created, and desktop features that depend on XDG_RUNTIME_DIR (file manager operations, PolicyKit prompts, audio) will fail.

Add the following line to /etc/pam.d/xrdp-sesman after the pam_loginuid.so line:

sed -i '/pam_loginuid.so/a session optional pam_systemd.so' /etc/pam.d/xrdp-sesman

4. Configure Session Managementโ€‹

Edit /etc/xrdp/sesman.ini and adjust the [Sessions] section for a multi-user VM. The default Policy=Default already supports session reconnect (users reconnect to their existing desktop rather than getting a new one). Adjust the reaping settings so disconnected sessions do not accumulate indefinitely:

[Sessions]
MaxSessions=100
KillDisconnected=true
DisconnectedTimeLimit=28800
SettingValueEffect
MaxSessions100Maximum concurrent sessions (active + disconnected). Set above your expected user count with headroom.
KillDisconnectedtrueTerminate sessions that have been disconnected longer than DisconnectedTimeLimit
DisconnectedTimeLimit28800Seconds (8 hours) before a disconnected session is reaped. Users who reconnect within this window rejoin their existing desktop.
note

Do not add I (IP) to the Policy setting. tiCrypt traffic arrives through the tunnel infrastructure, so the source IP seen by XRDP is the tunnel endpoint, not the user's real IP. Adding I would prevent session reconnect.

5. Restrict Clipboard Redirectionโ€‹

By default XRDP shares the clipboard between the user's local machine and the remote desktop, which is a data-exfiltration path for secure-research VMs. To disable the clipboard entirely, set cliprdr=false in the [Channels] section of /etc/xrdp/xrdp.ini:

[Channels]
cliprdr=false

Then restart the XRDP services:

systemctl restart xrdp xrdp-sesman
Directional clipboard control (newer XRDP versions)

Newer XRDP can restrict the clipboard by direction, configured in the [Security] section of /etc/xrdp/sesman.ini (not xrdp.ini):

[Security]
RestrictOutboundClipboard=all
RestrictInboundClipboard=all

RestrictOutboundClipboard controls copying from the session out to the client (the data-exfiltration direction); RestrictInboundClipboard controls pasting into the session from the client. Both accept:

ValueEffect
noneNo restriction (default)
allBlock all clipboard transfer in that direction
text, file, imageBlock only that content type; combine as a comma-separated list, for example text,file

To let researchers paste into the VM but never copy anything out, block only the outbound direction and leave inbound unrestricted:

[Security]
RestrictOutboundClipboard=all
RestrictInboundClipboard=none
Confirm the clipboard restriction is actually in effect

Directional control is not available on older XRDP, and a version that does not recognize a key silently ignores it and leaves the clipboard open. Confirm the keys exist for your installed version with man sesman.ini (older releases exposed a similar setting in xrdp.ini), apply the change, restart with systemctl restart xrdp xrdp-sesman, then verify from a real session that copy and paste are actually blocked in the direction you intended.

6. Fix Startup Orderingโ€‹

The stock xrdp-sesman.service unit only declares After=network.target, which means it can start before D-Bus, NFS mounts, or the VM Controller's user provisioning are ready. This race condition causes intermittent RDP failures where some VMs from the same image work and others do not, or a VM works until its next restart.

Create systemd drop-in overrides to enforce correct ordering:

mkdir -p /etc/systemd/system/xrdp-sesman.service.d
cat > /etc/systemd/system/xrdp-sesman.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

mkdir -p /etc/systemd/system/xrdp.service.d
cat > /etc/systemd/system/xrdp.service.d/override.conf <<'EOF'
[Unit]
After=xrdp-sesman.service
Requires=xrdp-sesman.service

[Service]
Restart=on-failure
RestartSec=2
EOF

systemctl daemon-reload
danger

The Restart=on-failure directives are critical. Without them, if an RDP connection attempt arrives before the VM Controller has finished provisioning users, xrdp-sesman can crash or enter a bad state and systemd will not restart it. This produces a permanently broken RDP service on that VM while identical VMs launched from the same image work fine.

7. Disable NSS Caching Daemonsโ€‹

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

sssd and nscd cache name-service lookups, including negative results. If an RDP login attempt triggers a user lookup before the VM Controller has created that account, the "user not found" response is cached. Subsequent attempts fail even after the account exists, until the cache entry expires. On an isolated VLAN with no AD or LDAP, these services provide no benefit. Disable them so NSS lookups resolve directly from /etc/passwd.

SELinux enabled?

If the VM runs SELinux in enforcing mode, install the xrdp-selinux policy module. Without it, session spawning and home directory creation are blocked by AVC denials.

dnf install -y xrdp-selinux
rpm -q xrdp-selinux # verify it installed

The xrdp-selinux package is a weak dependency of xrdp and is silently skipped on minimal images or when using --setopt=install_weak_deps=False. Always verify it is present.

Troubleshooting SELinux denials

Check for denials:

ausearch -m avc -ts recent | grep -i -E "xrdp|sesman|xorg|oddjob"
SymptomCauseFix
Sessions fail to spawn a desktopxrdp-selinux module missingdnf install -y xrdp-selinux
Some VMs from the same image work, others don'tStale file labels from image cloningrestorecon -R -v /home /etc/xrdp /var/log/xrdp*
Home directory creation fails on first loginMissing xrdp-selinux (it allows oddjob_mkhomedir transitions)dnf install -y xrdp-selinux

If you build a golden VM image that is cloned for deployments, run a full relabel after finalizing the image:

touch /.autorelabel && reboot

If you change XRDP from the default port 3389, register the new port:

semanage port -a -t unreserved_port_t -p tcp <new-port>

As a last resort, generate a targeted policy from observed denials:

ausearch -m avc -ts recent | grep xrdp | audit2allow -M xrdplocal
semodule -i xrdplocal.pp

Review the generated .te file before loading -- audit2allow grants exactly what was denied, which may be overly permissive.

8. Enable Servicesโ€‹

systemctl enable xrdp xrdp-sesman

9. Configure controller.tomlโ€‹

The controller must allow port 3389 for RDP tunneling. Add the following to the [tunnel] section:

[tunnel]
allowedPorts = 3389

[tunnel.services]
rdp = 3389

10. Verifyโ€‹

systemctl status xrdp xrdp-sesman
ss -tlnp | grep 3389

XRDP should be listening on port 3389. A full end-to-end test requires a deployed VM with a provisioned user.