VNC
VNC serves a graphical desktop over the RFB protocol. It draws an X session, so it does not work with Wayland — the desktop and display manager must run on Xorg (see Troubleshooting).
On Ubuntu 22.04, RDP replaces VNC as the default remote-desktop protocol; VNC is still the lightest option for a headless server.
Contents
- Prerequisites
- Choosing a desktop
- Set up the VNC server
- xstartup
- Server options
- Connect over SSH
- Run as a systemd service
- Manage sessions
- Desktop apps and fixes
- Implementations
- Troubleshooting
Prerequisites
sudo apt update && sudo apt dist-upgrade -y
Reaching the server: SSH tunnel vs open port
Display :1 listens on TCP 5901, :2 on 5902, and so on.
- Preferred — bind to localhost and tunnel over SSH. Start the server with
-localhostso the port is not exposed, then forward it over SSH (see Connect over SSH). Nothing extra to open in the firewall, and the session is encrypted. - Direct — open the port. Only if you must connect without a tunnel, and ideally restrict it to your own IP. VNC's own transport is weakly encrypted.
# Direct connections only; prefer restricting the source
sudo ufw allow from YOUR_IP to any port 5901
# Or, unrestricted (least safe)
sudo ufw allow 5901
Choosing a desktop
| Desktop | Verdict |
|---|---|
| Xfce | Best experience on Ubuntu 22.04, at the cost of more RAM. Recommended. |
| LXDE | Lighter, but has a keyboard bug over VNC (arrow keys and Tab do not work) on Ubuntu 20.04/22.04. |
Set up the VNC server
Xfce with TigerVNC
Works well on Ubuntu 22.04.
sudo apt install -y xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils
sudo apt install -y tigervnc-standalone-server tigervnc-common
# Start once so ~/.vnc is created, then stop it to edit the config
vncserver :1
vncserver -kill :1
# Set the desktop that VNC launches
chmod u+x ~/.vnc/xstartup
vi ~/.vnc/xstartup # see the xstartup section below
LXDE with TightVNC
Runs on Ubuntu server 20.04/22.04 (tested on Linode and VMware). Note the keyboard bug above.
# Desktop environment (choose gdm3 if prompted). autocutsel enables copy & paste.
sudo apt install -y lxde-core xdg-utils xfonts-100dpi xfonts-75dpi lxterminal autocutsel
# X resources file
touch ~/.Xresources
# VNC server
sudo apt install -y tightvncserver
dpkg -l | grep vnc # confirm the version
# First start writes ~/.vnc/xstartup and prompts for a password.
# "Would you like to enter a view-only password (y/n)?" -> n
vncserver -localhost # bind to localhost (tunnel over SSH)
# or a specific display:
vncserver :1
vncpasswd # change or add a view-only password later
vncserver -kill :1 # stop a display
# Edit xstartup
cp ~/.vnc/xstartup ~/.vnc/xstartup.bak
chmod +x ~/.vnc/xstartup
vi ~/.vnc/xstartup
TightVNC is X-only and does not support multiple displays — you may see
Xlib: extension "RANDR" missing on display. Use TigerVNC if that matters.
xstartup
~/.vnc/xstartup decides which desktop the session runs. Replace its whole
contents.
Minimal (Xfce)
#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
exec startxfce4
General template (any desktop)
Uncomment the one session= line you want. Ends by killing the VNC server when
the session exits, so a clean logout tears the display down.
#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
export XKL_XMODMAP_DISABLE=1
# Show the VNC config tool (clipboard) in the top-left of the window
command -v vncconfig && vncconfig -iconic &
# Load X resources if present
[ -r "$HOME/.Xresources" ] && xrdb "$HOME/.Xresources"
# Pick one desktop
#session=startxfce4 # Xfce
session=startlxde # LXDE
#session=gnome-session # GNOME
#session='gnome-session --session=gnome-classic'
#session=mate-session # MATE
#session=startkde # KDE Plasma
#session=i3 # i3wm
# Copy & paste with the remote clipboard (needs autocutsel)
command -v autocutsel && autocutsel -fork
exec "$session"
vncserver -kill "$DISPLAY"
The older GNOME-oriented variant adds export XKL_XMODMAP_DISABLE=1 and runs
/etc/X11/Xsession before launching lxterminal & and
/usr/bin/lxsession -s LXDE & — use it only if the template above fails to
start a session.
Server options
~/.vnc/config sets defaults for every display started for this user.
# desktop=sandbox
geometry=1920x1080 # resolution
dpi=96
# localhost # listen on localhost only
# alwaysshared
Connect over SSH
Forward the remote VNC port to a local port through an SSH tunnel, then point
the viewer at localhost:59001.
# ssh -L <local port>:127.0.0.1:<remote port> -N -f -l <user> <remote ip>
ssh -L 59001:127.0.0.1:5901 -N -f -l root 129.112.197.165
-N runs no remote command, -f backgrounds the tunnel after authentication.
Tabby profile (Windows)
To launch the tunnel from a Tabby profile using PowerShell 7:
Program: C:\Program Files\PowerShell\7\pwsh.exe
Arguments: -Command & ssh -L 59001:127.0.0.1:5901 -N -f -l root <remote ip>
One-line equivalent:
'C:\Program Files\PowerShell\7\pwsh.exe' -nologo -Command '& ssh -L 59001:127.0.0.1:5901 -N -f -l root <remote ip>'
On Windows, kill leftover ssh.exe processes if a previous tunnel did not
disconnect cleanly, or the local port stays busy.
Run as a systemd service
Start the server automatically at boot (example for TigerVNC).
vncserver -kill :1
sudo vi /etc/systemd/system/[email protected]
/etc/systemd/system/[email protected]:
[Unit]
Description=Start vnc server at startup
After=syslog.target network.target
[Service]
Type=forking
User=root
Group=root
WorkingDirectory=/root
# Some systemd versions no longer need PIDFile
#PIDFile=/root/.vnc/%H:%i.pid
ExecStartPre=-/usr/bin/vncserver -kill :%i > /dev/null 2>&1
ExecStart=/usr/bin/vncserver -depth 24 -geometry 1280x800 -localhost :%i
ExecStop=/usr/bin/vncserver -kill :%i
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
# The number after @ is the display, so vncserver@1 serves :1 on port 5901.
sudo systemctl enable [email protected]
sudo systemctl start vncserver@1
sudo systemctl status vncserver@1
Manage sessions
# List running sessions / listeners
vncserver -list
ps -ef | grep vnc | grep -v grep
sudo netstat -lnp | grep -i vnc
# Read a session log
less ~/.vnc/HOSTNAME:1.log
# Kill a display
vncserver -kill :1
Removing the systemd service completely:
systemctl stop [email protected] # may take a moment to respond
systemctl disable [email protected]
ls /etc/systemd/system/vnc*
sudo rm /etc/systemd/system/$VNC_SERVICE # and any related symlinks
sudo rm /usr/lib/systemd/system/$VNC_SERVICE # and any related symlinks
systemctl daemon-reload
systemctl reset-failed
Desktop apps and fixes
Locale and fonts
sudo apt install locales
sudo dpkg-reconfigure locales # generate the locales you need
# Default language
echo 'export LANG=en_US.UTF-8' >> ~/.bashrc
# CJK fonts
sudo apt install -y fonts-wqy-zenhei xfonts-wqy
Firefox
sudo apt install -y firefox
firefox
If it fails with cannot open display or Client is not authorized to connect
to Server, X access control is blocking it. Grant access, preferring the
narrower form:
xhost +localhost # allow local connections only (safer)
# xhost + # disables access control entirely — avoid
# Or run Firefox with the right X authority cookie
alias firefox='XAUTHORITY=$HOME/.Xauthority firefox'
xhost + turns off X access control for everyone — use +localhost or
+SI:localuser:USER instead.
Other
# xdg-settings: not found
sudo apt install xdg-utils
# pCloud desktop needs FUSE
sudo apt install libfuse2
Viewer tip: colour depth
Washed-out colours are usually the viewer down-sampling. In the client: Properties ▸ Options ▸ General ▸ Picture quality: High.
Implementations
| Server | Notes |
|---|---|
| TigerVNC | Fork of TightVNC; the usual choice on Linux. Supports RANDR / multiple displays. |
| TightVNC | Lightweight, X-only, single display. |
| TurboVNC | Adds VirtualGL support for GPU-accelerated / 3D workloads. |
| RemoteVNC | NAT and firewall pass-through. |
| RealVNC | Commercial, from VNC's original authors. |
Troubleshooting
Wayland is not supported
VNC draws an X session and cannot attach to Wayland. Force Xorg by editing
/etc/gdm3/custom.conf and uncommenting:
WaylandEnable=false
Then restart the display manager (or reboot).
Cannot start Xtightvnc / font path
Couldn't start Xtightvnc; trying default font path.
Please set correct fontPath in the vncserver script.
Usually a stale display. Kill it and restart:
vncserver -list
vncserver -kill :1
vncserver :1
Windows: local port stays busy
A previous tunnel left an ssh.exe running — end it in Task Manager (or
taskkill /F /IM ssh.exe) before reconnecting.