How to Create a Virtual (Headless) TigerVNC Server on Ubuntu 20.04

Creating a headless virtual VNC session (i.e. no monitor plugged in) for Ubuntu 20.04 using GNOME was harder than anticipated. It was easy to set up a local VNC session, but going headless without a dummy adapter AND with the GNOME desktop was a challenge.

Install TigerVNC

Some VNC servers only support local VNC sessions (i.e. controlling the visible logged-in desktop). Others can support virtual (remote) displays that are truly headless. We want the latter. TigerVNC supports virtual displays, is an active successor to TIghtVNC, and is easy to install from the Ubuntu repositories

sudo apt install tigervnc-standalone-server

Setup VNC password for the user.

vncpasswd

Test the installation by starting and then killing the server.

vncserver :1
vncserver -kill :1

Configure the VNC Server

We want to run the VNC server as a system service (starts at boot), using GNOME (i.e. not XFCE4). Instructions found in other tutorials might work if you manually start the VNC server, but you will get a black screen if starting it as a service with GNOME (although it works with XFCE4). The fix comes from this post.

Create ~/.vnc/xstartup, edit the file, make executable

cd ~/.vnc
pico xstartup
#!/bin/sh
[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
chmod 755 xstartup

Create /etc/vnc/xstartup, edit the file, make executable. This will create the typical Ubuntu desktop.

sudo mkdir /etc/vnc
cd /etc/vnc
sudo pico xstartup
#!/bin/sh

test x"$SHELL" = x"" && SHELL=/bin/bash
test x"$1"     = x"" && set -- default

vncconfig -iconic &
"$SHELL" -l << EOF
export XDG_SESSION_TYPE=x11
export GNOME_SHELL_SESSION_MODE=ubuntu
dbus-launch --exit-with-session gnome-session --session=ubuntu
EOF
vncserver -kill $DISPLAY
chmod 755 xstartup

Create /etc/systemd/system/vncserver@.service and start the service

sudo pico /etc/systemd/system/vncserver@.service
[Unit]
Description=Start TigerVNC server at startup
After=syslog.target network.target

[Service]
Type=simple
User=USERNAME
PAMName=login
PIDFile=/home/USERNAME/.vnc/%H:%i.pid
ExecStartPre=/usr/bin/vncserver -kill :%i > /dev/null 2>&1
ExecStart=/usr/bin/vncserver -fg -depth 24 -geometry 1920x1200 -localhost no :%i
ExecStop=/usr/bin/vncserver -kill :%i

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable vncserver@1.service
sudo systemctl start vncserver@1
sudo systemctl status vncserver@1

Connect Using Remmina VNC Client

If using Ubuntu 20.04 as the client machine, Remmina (a VNC client) is installed by default. Enter the VNC connection settings under the basic tab (e.g. COMPUTERNAME.local:1, :1 corresponds to the display number above). Enter the SSH settings under the SSH Tunnel tab. Remmina is convenient because it handles the SSH tunnel. As long as the SSH port has been opened, you shouldn’t have to configure anything else (e.g. opening up port 5901 on the server).