Install and Configure TigerVNC Server on RHEL 8/9

Install and configure TigerVNC Server on RHEL 8 and 9, Rocky Linux, AlmaLinux, and CentOS Stream using user mappings, systemd, GNOME, firewalld, and secure VNC connections.

Published

Updated

Read time 15 min read

Reviewed byDeepak Prasad

TigerVNC remote desktop session running between RHEL-family server and client

TigerVNC gives each configured Linux user an independent virtual desktop. That is separate from mirroring whoever happens to be logged in at the physical console.

This walkthrough covers the modern workflow on RHEL 8.3 and later, RHEL 9, Rocky Linux 8/9, AlmaLinux 8/9, and CentOS Stream 9. You map users in /etc/tigervnc/vncserver.users and start the packaged vncserver@:<display>.service unit—no hand-copied systemd files.

Validation environment: Rocky Linux 9.8 (Blue Onyx) with tigervnc-server-1.15.0-7.el9_8.2. Package installation, the vncserver@:2.service unit, configuration parsing, and socket binding were validated in a systemd-enabled test container. GNOME desktop rendering, remote viewer authentication, host firewalld behavior, native-host SELinux labels, and NVIDIA integration require validation on a VM or physical server.

bash
rpm -q tigervnc-server
output
tigervnc-server-1.15.0-7.el9_8.2.x86_64

Steps follow Red Hat Enterprise Linux 9 — Remotely accessing the desktop as multiple users. Rocky Linux 10.2 no longer ships tigervnc-server; on RHEL 10 use GNOME Remote Desktop with RDP instead.

IMPORTANT
RHEL 10 removed TigerVNC server packages. For graphical remote access on RHEL 10, Rocky Linux 10, or AlmaLinux 10, use the RHEL 10 remote desktop guide with GNOME Remote Desktop and RDP rather than this TigerVNC procedure.

Quick reference

Task Command or file
Map user to display /etc/tigervnc/vncserver.users:2=vncuser
System-wide session defaults /etc/tigervnc/vncserver-config-defaults
Set VNC password vncpasswd as the mapped user
Start display :2 sudo systemctl enable --now vncserver@:2.service
Identify active firewall zone sudo firewall-cmd --get-active-zones
Set the zone for later commands ZONE=public — replace public with the active interface zone
Open one VNC port (direct access) sudo firewall-cmd --zone="$ZONE" --permanent --add-port=5902/tcp
Connect from client vncviewer --shared server-ip:2
Check service logs sudo journalctl -u vncserver@:2.service
Restore SELinux labels Run restorecon as the mapped VNC user on the VNC directories that exist

TigerVNC Support Across RHEL Versions

Platform TigerVNC status Configuration method
CentOS Linux 7 End of life Legacy custom [email protected] units
RHEL 7 End of normal maintenance; ELS available for RHEL 7.9 Legacy custom [email protected] units
RHEL 8.0–8.2 Older workflow Legacy user-specific systemd configuration
RHEL 8.3 and later Available vncserver.users and packaged systemd template
RHEL 9 Available but deprecated Same modern workflow
Rocky/AlmaLinux 8 and 9 Available in current repositories User mapping and packaged systemd unit
CentOS Stream 9 Available User mapping and packaged systemd unit
RHEL 10 / Rocky 10 / AlmaLinux 10 TigerVNC removed GNOME Remote Desktop with RDP
NOTE
TigerVNC remains available on RHEL 9, but Red Hat has deprecated it. This procedure is appropriate for existing RHEL 8/9-family deployments, but plan to migrate to GNOME Remote Desktop with RDP before moving those systems to RHEL 10.

This tutorial follows the RHEL 8.3+ and RHEL 9 method documented in Red Hat's multi-user VNC procedure for RHEL 9 and RHEL 8 remote desktop access. Supported Rocky Linux and AlmaLinux 8 installations are based on the later RHEL 8 lifecycle, so RHEL 8.0–8.2 differences are noted here only. A short RHEL 7 legacy note appears at the end for old installations only.


How TigerVNC Displays and Ports Work

Display TCP port Typical use
:0 5900 Existing local graphical display
:1 5901 Available, but not the recommended first mapped user
:2 5902 Recommended first TigerVNC user
:3 5903 Second TigerVNC user

The formula is:

text
TCP port = 5900 + display number

Display :2 maps to TCP port 5902. Give each VNC user a unique display number. Several viewers can attach to the same display, but they all see one desktop. To keep more than one viewer connected at once, set alwaysshared on the server or pass --shared from the client; otherwise a new non-shared connection may drop the existing viewers. A TigerVNC session is independent of the physical console.

Do not configure root as a VNC desktop user. This walkthrough uses one regular account named vncuser.


Install TigerVNC and the Desktop Environment

Identify the operating system before you install packages:

bash
grep -E '^(NAME|VERSION)=' /etc/os-release
output
NAME="Rocky Linux"
VERSION="9.8 (Blue Onyx)"

Confirm that the host is one of the supported RHEL 8.3+/9-family releases listed above before continuing.

Install the TigerVNC server package:

bash
sudo dnf install tigervnc-server

Install the viewer only when this server will also act as a VNC client:

bash
sudo dnf install tigervnc

A headless or minimal server still needs a graphical desktop environment before TigerVNC can start GNOME. See what environment groups are available:

bash
dnf grouplist -v --available

On many RHEL-family servers you install a workstation or server-with-GUI environment, for example:

bash
sudo dnf groupinstall "Server with GUI"

Check the desktop sessions available to TigerVNC:

bash
ls -1 /usr/share/xsessions/

Example on Rocky Linux 9.8 with GNOME installed:

output
gnome-custom-session.desktop
gnome-xorg.desktop
gnome.desktop

Use the filename without .desktop as the session value. For example, gnome.desktop corresponds to:

text
session=gnome

A TigerVNC virtual session can run on a headless server once the chosen desktop session packages are installed; you do not need an active local console login for every mapped user.

Verify the packaged files and systemd template:

bash
rpm -ql tigervnc-server | grep -E 'vncserver|tigervnc'
bash
systemctl cat [email protected]

On RHEL 8 and 9, leave the packaged unit in /usr/lib/systemd/system/. Map users in /etc/tigervnc/vncserver.users instead of copying and editing the unit under /etc/systemd/system/.


Create and Map the VNC User

Create a regular Linux account for the VNC session. Do not use root:

bash
sudo useradd -m vncuser
bash
sudo passwd vncuser

Map the account to display :2 in /etc/tigervnc/vncserver.users:

text
:2=vncuser

For multiple users:

text
:2=vncuser
:3=developer

The display number sets both the systemd instance name (vncserver@:2.service) and the TCP port. A user who is already logged into the local graphical session cannot also export the same kind of VNC session.


Configure the TigerVNC Session

Edit system-wide defaults in /etc/tigervnc/vncserver-config-defaults:

text
session=gnome
alwaysshared
geometry=1920x1080
  • session=gnome picks the desktop from /usr/share/xsessions/
  • alwaysshared lets multiple viewers stay on the same display
  • geometry sets the virtual desktop size

TigerVNC currently permits TLSVnc and VncAuth by default. Declaring the same list explicitly documents the policy but does not harden it:

text
securitytypes=tlsvnc,vncauth

TLSVnc encrypts the VNC connection and then uses VNC password authentication. It does not provide the same certificate-based server identity verification as the X509Vnc security type.

Because VncAuth is still available without TLS transport protection, use direct access only on a trusted network. For untrusted networks, prefer the SSH-only configuration below.

Starting with TigerVNC 1.14, upstream TigerVNC uses the XDG-based per-user configuration file:

text
~/.config/tigervnc/config

Older RHEL-family packages use:

text
~/.vnc/config

Some RHEL documentation still shows the older path in its configuration priority list. Check the installed package's vncsession manual and the directories your user actually has before editing per-user settings:

bash
man vncsession
bash
sudo -iu vncuser sh -c 'find "$HOME/.config/tigervnc" "$HOME/.vnc" -maxdepth 1 -type f 2>/dev/null'

TigerVNC reads configuration files in this order:

  1. /etc/tigervnc/vncserver-config-defaults
  2. ~/.config/tigervnc/config (or legacy ~/.vnc/config)
  3. /etc/tigervnc/vncserver-config-mandatory

The mandatory file is loaded last, so it wins when the same option appears in more than one place.

TigerVNC 1.14 and later normally store:

text
~/.config/tigervnc/config
~/.config/tigervnc/passwd
~/.local/state/tigervnc/

Confirm paths on your host with rpm -ql tigervnc-server, man vncsession, and journalctl -u vncserver@:2.service.

Do not rely on hand-edited ~/.vnc/xstartup GNOME or KDE blocks as the main configuration method on RHEL 8/9.

If the display instance is already running, restart it after changing system-wide or per-user options:

bash
sudo systemctl restart vncserver@:2.service

During initial setup, continue to the service startup section below.

The password file can be updated while Xvnc is running because TigerVNC reads it for each incoming connection. Other server settings need a service restart to take effect once the unit is running.

NOTE

If the server uses the proprietary NVIDIA driver, TigerVNC may require an Xorg-based GNOME session. In /etc/gdm/custom.conf, uncomment:

text
WaylandEnable=False

Then add this under [daemon]:

text
DefaultSession=gnome-xorg.desktop

Reboot the server after changing the display-manager configuration.


Set the VNC Password and Restore SELinux Contexts

Switch to the mapped user and set a VNC password:

console
[root@server ~]# sudo -iu vncuser
[vncuser@server ~]$ vncpasswd

The VNC password is separate from the Linux account password. TigerVNC stores an obfuscated value in a user-readable-only file, but this is not secure encryption. Use a unique password and protect the transport with TLS, a trusted network, VPN, or an SSH tunnel.

On TigerVNC 1.14 and later, verify the password file:

bash
ls -lZ ~/.config/tigervnc/passwd

Confirm that:

  • The file belongs to the mapped VNC user
  • Its mode is 600
  • It has the default SELinux context for its location

If the context is unexpected, restore it before changing or disabling SELinux:

bash
restorecon -v ~/.config/tigervnc/passwd

If you are migrating an older configuration, restore labels on the directories that exist:

bash
restorecon -RFv ~/.vnc
bash
restorecon -RFv ~/.config/tigervnc
bash
restorecon -RFv ~/.local/state/tigervnc

Return to the administrative shell:

bash
exit

The current TigerVNC layout places the password under ~/.config/tigervnc/passwd and state such as logs under ~/.local/state/tigervnc/.

IMPORTANT

For traditional TigerVNC password authentication, the password must contain at least six characters, but only the first eight characters are significant. Do not assume that adding characters after the eighth strengthens VncAuth.

Use a unique VNC password and protect the connection with TLS, a VPN, or the SSH-only method described below.


Configure firewalld and Secure Network Access

Pick one access model and stick with it. An SSH tunnel encrypts traffic through the tunnel, but it does not stop direct VNC connections while Xvnc listens on all interfaces and firewalld still permits VNC ports.

Identify the zone attached to the network interface VNC clients use:

bash
sudo firewall-cmd --get-active-zones

Set the applicable zone name. Replace public when your interface uses another zone:

bash
ZONE=public

Option A: Direct connection on a trusted network

For direct access to display :2, add TCP port 5902 to that zone:

bash
sudo firewall-cmd --zone="$ZONE" --permanent --add-port=5902/tcp
bash
sudo firewall-cmd --reload

Opening only 5902/tcp is tighter than enabling the predefined 59005903 range when you have a single VNC user.

Verify the same zone:

bash
sudo firewall-cmd --zone="$ZONE" --list-ports
output
5902/tcp

For multiple mapped users on displays :2 through :3, enable the predefined service in that zone:

bash
sudo firewall-cmd --zone="$ZONE" --permanent --add-service=vnc-server
bash
sudo firewall-cmd --reload
bash
sudo firewall-cmd --zone="$ZONE" --list-services
output
vnc-server

To see which ports that service represents:

bash
sudo firewall-cmd --info-service=vnc-server
output
vnc-server
  ports: 5900-5903/tcp

--info-service describes the service definition; --zone="$ZONE" --list-services and --zone="$ZONE" --list-ports show what is actually enabled on the interface zone. For displays above :3, open the calculated port in the same zone—for example, display :5 needs TCP port 5905:

bash
sudo firewall-cmd --zone="$ZONE" --permanent --add-port=5905/tcp
bash
sudo firewall-cmd --reload

Restrict sources to a trusted administration network when you can instead of exposing VNC to the internet.

Option B: SSH tunnel without exposing VNC

To enforce local-only listening for VNC sessions started through the packaged vncserver@ service, add this option to /etc/tigervnc/vncserver-config-mandatory:

text
localhost

The mandatory file overrides per-user TigerVNC configuration, so users cannot re-enable external listening through ~/.config/tigervnc/config.

This file is an administrative policy for the packaged vncsession workflow, not a complete security boundary against users who can run Xvnc directly. Keep unnecessary VNC ports closed in firewalld and limit shell access appropriately.

If vncserver@:2.service is already running, restart it after adding localhost:

bash
sudo systemctl restart vncserver@:2.service

During initial setup, enable and start the service in the next section after you finish firewall and mandatory-file changes.

Do not open TCP port 5902 in firewalld for this method. Remove whichever firewalld rule you added earlier in the same zone.

If you opened only TCP port 5902:

bash
sudo firewall-cmd --zone="$ZONE" --permanent --remove-port=5902/tcp

If you enabled the predefined VNC service:

bash
sudo firewall-cmd --zone="$ZONE" --permanent --remove-service=vnc-server

Reload and verify the same zone:

bash
sudo firewall-cmd --reload
bash
sudo firewall-cmd --zone="$ZONE" --list-ports
bash
sudo firewall-cmd --zone="$ZONE" --list-services

Neither 5902/tcp nor vnc-server should appear when you chose SSH-only access.

Confirm Xvnc listens only on loopback:

bash
sudo ss -lntp | grep ':5902'
output
LISTEN 0  5  127.0.0.1:5902  0.0.0.0:*  users:(("Xvnc",pid=1873,fd=9))
LISTEN 0  5     [::1]:5902     [::]:*  users:(("Xvnc",pid=1873,fd=10))

Create the tunnel from your client. SSH authentication is separate from VNC authentication—the SSH account can be a dedicated administrator or bastion user; vncuser does not need interactive SSH access:

bash
ssh -N -L 5902:127.0.0.1:5902 admin@server-ip

Any authorized SSH account for which TCP forwarding is permitted can establish the tunnel. If SSH reports that forwarding is prohibited, check AllowTcpForwarding, PermitOpen, and applicable Match blocks in the SSH server configuration.

Connect through the tunnel:

bash
vncviewer --shared localhost:2

If TCP port 5902 is already in use on the client, pick another local port while keeping the remote side on 5902:

bash
ssh -N -L 15902:127.0.0.1:5902 admin@server-ip
bash
vncviewer --shared localhost::15902

This is the safer choice across an untrusted network. For a longer SSH tunneling walkthrough, see SSH port forwarding. For broader firewall workflows, see the firewalld cheat sheet.


Start, Enable, and Connect to TigerVNC

Enable and start display :2:

bash
sudo systemctl enable --now vncserver@:2.service

Check that the unit is active:

bash
systemctl is-active vncserver@:2.service
output
active

See which address Xvnc is listening on:

bash
sudo ss -lntp | grep ':5902'

With direct network access (no localhost in the mandatory file), Xvnc listens on all interfaces:

output
LISTEN 0  5  0.0.0.0:5902  0.0.0.0:*  users:(("Xvnc",pid=1364,fd=9))
LISTEN 0  5     [::]:5902     [::]:*  users:(("Xvnc",pid=1364,fd=10))

With localhost in vncserver-config-mandatory, the listener is bound to loopback only—see the SSH-only section above for that sample.

The listener output above was collected from a Rocky Linux 9.8 systemd-enabled test container after starting vncserver@:2.service. That confirms package install, unit startup, and socket binding. It does not prove GNOME desktop startup, remote viewer login, session persistence, or host firewalld behavior—validate those on a VM or physical server before you rely on this in production.

Review the unit status and journal when something fails to start:

bash
sudo systemctl status vncserver@:2.service
bash
sudo journalctl -u vncserver@:2.service

From a machine that can reach the server (or through an SSH tunnel), connect with a TigerVNC client:

bash
vncviewer --shared server-ip:2

--shared tells the server to keep existing viewer connections. With alwaysshared already set, other viewers may still connect, but passing --shared makes the client intent explicit.

Confirm on your setup that:

  • The expected user's GNOME desktop appears
  • The session survives closing and reopening the viewer
  • Multiple viewers stay connected when alwaysshared is enabled
  • You are using the correct display number and port
Action Command
Start sudo systemctl start vncserver@:2.service
Stop sudo systemctl stop vncserver@:2.service
Restart sudo systemctl restart vncserver@:2.service
Disable at boot sudo systemctl disable vncserver@:2.service
Status sudo systemctl status vncserver@:2.service

Configure Multiple Users or Per-User Options

Add another mapping:

text
:3=developer

Then run vncpasswd as developer, start vncserver@:3.service, open TCP port 5903 if you use direct access, and connect to display :3.

The per-user ~/.config/tigervnc/config file (or legacy ~/.vnc/config) can override settings in vncserver-config-defaults when different users need different resolution, desktop name, sharing behavior, or session type. Restart each affected display after you change per-user options.


Troubleshoot TigerVNC Server Problems

Symptom Likely cause Check or recovery
Unit vncserver@:2.service failed Invalid user mapping or session configuration Check vncserver.users and the journal
Black or blank screen Invalid desktop session or missing GNOME packages Verify installed session and session= value
Connection refused Service not listening or firewall blocking port Check systemctl, sudo ss, and firewall-cmd --zone="$ZONE" --list-ports
Wrong user desktop opens Incorrect display-to-user mapping Check /etc/tigervnc/vncserver.users
Password rejected Password was set as another user Run vncpasswd as the mapped user
Permission denied Wrong ownership or SELinux labels Check home-directory ownership and run restorecon
Display already in use Existing X or stale VNC session Check processes, sockets, and service status
Service works manually but not through systemd Old custom unit overrides packaged service Remove obsolete unit overrides and run daemon-reload
GNOME session exits immediately Missing desktop components or session mismatch Review journal and available session files
GNOME fails with proprietary NVIDIA driver Wayland is enabled Disable Wayland, select gnome-xorg.desktop, and reboot
Cannot connect above display :3 vnc-server firewalld service does not cover the higher port Open the calculated TCP port manually
SSH tunnel reports administratively prohibited SSH TCP forwarding is disabled Check AllowTcpForwarding, PermitOpen, and account-specific Match rules

Useful diagnostics:

bash
sudo systemctl status vncserver@:2.service
bash
sudo journalctl -xeu vncserver@:2.service
bash
sudo ss -lntp
bash
sudo firewall-cmd --zone="$ZONE" --list-all
bash
loginctl list-sessions
bash
rpm -V tigervnc-server

systemctl status shows whether the unit is active. journalctl surfaces session startup errors. sudo ss -lntp confirms the listener on port 5902. firewall-cmd --zone="$ZONE" --list-all shows whether vnc-server or a custom port is open on the zone attached to your VNC interface.


Migrating from the RHEL 7 TigerVNC Configuration

RHEL 7 used copied unit files such as:

text
/etc/systemd/system/[email protected]
/etc/systemd/system/vncserver@:1.service
/etc/systemd/system/vncserver@:2.service

On RHEL 8.3+ and RHEL 9:

  1. Record existing users, displays, geometry, and passwords
  2. Stop and disable old instances—for example sudo systemctl disable --now vncserver@:2.service—and repeat for each configured display number
  3. Back up and remove only obsolete TigerVNC unit files such as [email protected] and copied vncserver@:<display>.service instances under /etc/systemd/system/. Do not remove unrelated systemd service files
  4. Run sudo systemctl daemon-reload
  5. Map users in /etc/tigervnc/vncserver.users
  6. Move common settings to vncserver-config-defaults
  7. Run restorecon as each mapped VNC user on the TigerVNC directories that exist
  8. Start the packaged vncserver@:<display>.service unit
  9. Test one user before migrating additional accounts

Do not reuse RHEL 7 vncserver_wrapper, runuser, or custom PIDFile examples in the main RHEL 8/9 workflow. On RHEL 7, a Resource limit exceeded failure often involves stale files under /tmp or /tmp/.X11-unix/—see vncserver resource limit exceeded.


Summary

On RHEL 8.3+, RHEL 9, Rocky Linux 8/9, AlmaLinux 8/9, and CentOS Stream 9:

  1. Install tigervnc-server and a GNOME desktop environment
  2. Create a regular account such as vncuser—not root
  3. Map the user to display :2 in vncserver.users
  4. Set session=gnome in vncserver-config-defaults
  5. Run vncpasswd as the mapped user
  6. Open TCP port 5902 in the correct firewalld zone for direct access, or set localhost in vncserver-config-mandatory and use an SSH tunnel
  7. Enable vncserver@:2.service
  8. Connect with vncviewer --shared server-ip:2 and confirm the desktop loads on your network

On RHEL 10 and compatible 10.x distributions, use GNOME Remote Desktop with RDP instead of TigerVNC. For SSH-protected access, see SSH port forwarding. For firewall management beyond VNC, see the firewalld cheat sheet.


References

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …