Install the packages
sudo apt update && sudo apt upgrade
sudo apt install tightvncserver xfce4 xfce4-goodies xorg dbus-x11 \
x11-xserver-utils autocutsel
XFCE is lightweight enough to run smoothly even on a 1 GB VPS. GNOME is an option but it's significantly heavier and its compositor sometimes misbehaves under VNC. autocutsel enables clipboard sharing between the VNC session and your local machine.
Create a dedicated VNC user
Running a desktop session as root is a bad idea — some applications (notably Chrome / Chromium) refuse to launch as root, and a compromised graphical session has much more reach as root than as a normal user.
sudo adduser vncuser
sudo usermod -aG sudo vncuser # optional: if this user needs sudo
Initialize the VNC password
sudo -u vncuser bash -c 'vncserver'
# It prompts for a password (max 8 chars — a TightVNC quirk).
# It also offers to set a view-only password — useful for pair debugging; say 'n' otherwise.
sudo -u vncuser bash -c 'vncserver -kill :1'
The init puts the password in /home/vncuser/.vnc/passwd (mode 600). It also generates a default xstartup that launches an ancient twm session — we'll replace that next.
Configure the XFCE session
sudo -u vncuser tee /home/vncuser/.vnc/xstartup <<'EOF'
#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
# Enable clipboard sharing
autocutsel -fork
# Start XFCE
startxfce4 &
EOF
sudo chmod +x /home/vncuser/.vnc/xstartup
TightVNC's authentication exchange is decent, but the actual screen contents and keystrokes after connect are not encrypted. Never expose the VNC port (5901) directly to the internet — always reach it through an SSH tunnel (see below) or a VPN.
systemd service (instead of the old init.d script)
The 2019 version of this tutorial used a SysV init script. On any Debian version released since then, systemd is the right tool. Drop in a user-templated service:
sudo tee /etc/systemd/system/vncserver@.service <<'EOF'
[Unit]
Description=TightVNC server on display %i
After=syslog.target network.target
[Service]
Type=forking
User=%i
Group=%i
WorkingDirectory=/home/%i
PIDFile=/home/%i/.vnc/%H:1.pid
ExecStartPre=-/usr/bin/vncserver -kill :1 > /dev/null 2>&1
ExecStart=/usr/bin/vncserver :1 -geometry 1920x1080 -depth 24 -localhost yes
ExecStop=/usr/bin/vncserver -kill :1
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now vncserver@vncuser.service
sudo systemctl status vncserver@vncuser.service
The key flag here is -localhost yes. It binds VNC to 127.0.0.1 only, refusing any direct external connection. Combined with the SSH tunnel on the client side, this closes the "someone found my VNC port with Shodan" class of problem.
Connect from the client via SSH tunnel
From your local machine:
ssh -L 5901:localhost:5901 vncuser@your-server
Leave that session open. Then point your VNC client at localhost:5901. Traffic tunnels through SSH — encrypted, authenticated, and invisible to anyone watching the wire.
Good VNC clients: TightVNC Viewer or TigerVNC Viewer on Windows, remmina or vinagre on Linux, RealVNC Viewer on macOS.
Multiple users
To add a second VNC session for a different user, create the vncuser2 account, initialize the password as above, and enable the same unit with a different instance name:
sudo systemctl enable --now vncserver@vncuser2.service
By convention, first user on :1 (port 5901), second on :2 (5902), etc. Edit the unit's ExecStart to pass :2 for the second one, or just create a separate unit file.
When VNC is the wrong tool
X2Go — if both ends are Linux, X2Go is strictly better than VNC: it uses NX compression (much faster on slow links), runs over SSH natively (no separate tunnel), resumes sessions across disconnects, and handles per-app windowing (not just whole-desktop). sudo apt install x2goserver x2goserver-xsession on the server, the x2goclient on the desktop. Done.
noVNC — if you need zero-install access from any browser, noVNC wraps VNC in a WebSockets proxy you can reach over HTTPS. Good for occasional access from a machine where you can't install a VNC client.
RDP (xrdp) — if your clients are Windows users who already know Remote Desktop, xrdp gives them the familiar RDP experience against a Linux host. Works with Windows' built-in client.
For anything production-facing I'd reach for X2Go or RDP first. VNC's strength is that it's universal — every VNC viewer in the world can connect, and sometimes that's what matters.