Skip to main content

Just Another VNC TLS+Passwd Auth and SSH Tunnel Setup

·1144 words·6 mins
Abraham Cabrera
Author
Abraham Cabrera
Builder. Thinker. Automator. I home-lab, code things, break them, fix them, then write about it.

Ubuntu 24.04 Cinnamon VNC Setup (Cinnamon) with SSH Tunnel
#

Introduction
#

I know this is yet another VNC blog post among many out there, but I wanted to document the exact steps I took to set up my local infrastructure for future reference. Writing things down helps me keep track of what I’m doing and how I did it especially when I revisit something months later.

Below are the steps I used to configure two Ubuntu 24.04 systems (server & client) running Cinnamon for secure SSH-tunneled VNC connectivity with TLS and Password authentication.


Why This Setup?
#

My goal was to support two use cases:

  • A virtual desktop session that runs independently of the physical display (useful for headless or persistent sessions)
  • A shared display session that connects to the live desktop

TigerVNC supports both models cleanly, and when paired with SSH tunneling, it keeps everything private without exposing VNC ports directly to the network.


Overview
#

Two VNC configurations are set up on remote server media-01:

  • Virtual desktop (:2, port 5902) — isolated session via tigervncserver
  • Shared display (:1, port 5901) — live desktop session via x0vncserver

Both are bound to localhost only and accessed via SSH tunnel.

Architecture (Simplified)
#

Client
  |
SSH Tunnel (5901 / 5902)
  |
TLS (X509Vnc + Password)
  |
media-01
  ├─ x0vncserver (:1)    -> shared live display
  └─ tigervncserver (:2) -> virtual desktop

Server Prerequisites
#

Static IP Configuration
#

Before setting up VNC, I configured a static IP for media-01 to ensure it is always reachable at 192.168.2.124. This was done via my router.

Static IP configuration

Packages
#

1sudo apt install tigervnc-standalone-server
2sudo apt install tigervnc-scraping-server
3sudo apt install dbus-x11

Password Setup
#

1vncpasswd ~/.vnc/passwd
2chmod 600 ~/.vnc/passwd

Note: VNC passwords are limited to 8 characters and use weak DES obfuscation. File permissions plus SSH tunneling are the real security controls here.


Desktop Session Startup Script
#

Create ~/.vnc/xstartup:

1#!/bin/bash
2exec /usr/bin/cinnamon-session

Make it executable:

1chmod +x ~/.vnc/xstartup

Determining the Correct DISPLAY
#

Do not assume the physical display is :1. On many Ubuntu systems it is :0.

Verify using:

1echo $DISPLAY
2loginctl list-sessions

Adjust your systemd service accordingly.


Systemd Service — Virtual Desktop (:2)
#

I created a systemd file at /etc/systemd/system/tigervncserver@.service so that the virtual VNC server is always online on startup of Ubuntu:

 1[Unit]
 2Description=TigerVNC Server
 3After=network.target
 4
 5[Service]
 6Type=forking
 7User=box02
 8WorkingDirectory=/home/box02
 9
10PIDFile=/home/box02/.vnc/%H:%i.pid
11ExecStartPre=-/usr/bin/vncserver -kill :%i
12ExecStart=/usr/bin/vncserver :%i -localhost -SecurityTypes X509Vnc -X509Key /home/box02/.vnc/x509_key.pem -X509Cert /home/box02/.vnc/x509_cert.pem -PasswordFile /home/box02/.vnc/passwd
13ExecStop=/usr/bin/vncserver -kill :%i
14
15Restart=on-failure
16RestartSec=5
17
18# Optional hardening
19ProtectSystem=full
20ProtectHome=true
21PrivateTmp=true
22NoNewPrivileges=true
23
24[Install]
25WantedBy=multi-user.target

Enable and start:

1sudo systemctl daemon-reload
2sudo systemctl enable tigervncserver@2.service
3sudo systemctl start tigervncserver@2.service

Systemd Service — Shared Display (:1)
#

I created a systemd file at /etc/systemd/system/x0vncserver.service so that the shared VNC server is always online on startup of Ubuntu:

 1[Unit]
 2Description=TigerVNC Shared Display Server
 3After=network.target
 4
 5[Service]
 6Type=simple
 7User=box02
 8Environment=DISPLAY=:1
 9Environment=XAUTHORITY=/home/box02/.Xauthority
10
11ExecStart=/usr/bin/x0vncserver -display :1 -localhost -fg -SecurityTypes X509Vnc -X509Key /home/box02/.vnc/x509_key.pem -X509Cert /home/box02/.vnc/x509_cert.pem -PasswordFile /home/box02/.vnc/passwd
12
13Restart=on-failure
14RestartSec=5
15
16ProtectSystem=full
17ProtectHome=true
18PrivateTmp=true
19NoNewPrivileges=true
20
21[Install]
22WantedBy=multi-user.target

Adjust DISPLAY if your system uses :0.


Useful Commands
#

Here are some commands I used on the remote server to restart and view service logs.

1journalctl -fu tigervncserver@2.service
2journalctl -fu x0vncserver.service
3
4sudo systemctl restart tigervncserver@2.service
5sudo systemctl restart x0vncserver.service
6
7vncserver -list

Client Setup
#

Install VNC viewer packages:

1sudo apt install tigervnc-viewer tigervnc-tools

SSH Key for Passwordless Tunnel
#

1ssh-keygen -t ed25519 -C "vnc-tunnel" -f ~/.ssh/media_01_id_ed25519
2ssh-copy-id -i ~/.ssh/media_01_id_ed25519.pub box02@192.168.2.124

Wrapper Connect Script
#

At this point I wanted a one-shot script to bring up the VNC session without having to remember everything involved, saved to ~/bin/vnc-connect:

 1#!/bin/bash
 2
 3set -e
 4
 5export SSH_SERVER=192.168.2.124
 6export REMOTE_PORT=5901
 7export LOCAL_PORT=5901
 8export IDENTITY_FILE="${HOME}/.ssh/media_01_id_ed25519"
 9export CONFIG="${HOME}/.config/tigervnc/media-01-shared.tigervnc"
10export PASSWD="${HOME}/.vnc/passwd"
11
12SSH_PID=$(pgrep -f "ssh -f -N -i ${IDENTITY_FILE} -L ${LOCAL_PORT}" || true)
13if [ -n "$SSH_PID" ]; then
14  echo "Lingering ssh tunnel found killing it now"
15  kill $SSH_PID
16else
17  echo "No ssh tunnel found starting one now"
18fi
19
20ssh -f -N -i ${IDENTITY_FILE} -L ${LOCAL_PORT}:127.0.0.1:${REMOTE_PORT} box02@${SSH_SERVER}
21sleep 1
22nohup xtigervncviewer -passwd "${PASSWD}" "${CONFIG}" 2>&1 > /tmp/vncviewer.log &

Make it executable:

1chmod +x ~/bin/vnc-connect

Ensure ~/bin is in PATH:

1echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
2source ~/.bashrc

Example Invocation
#

Below is an example of me invoking the one-shot script vnc-connect with the TigerVNC viewer session active.

vnc-connect invocation with TigerVNC viewer

Manual SSH Tunnel (Optional)
#

You can certainly tunnel manually and below is the command if you want to:

1ssh -L 5901:127.0.0.1:5901 box02@192.168.2.124 -N

Then connect your VNC viewer to 127.0.0.1:5901.


Addendum — TLS with X509Vnc
#

After the initial setup I decided to add TLS encryption to both VNC servers using TigerVNC’s built-in X509Vnc security type. Since I was already using SSH tunneling, this is more of a defense-in-depth measure than a strict necessity — but it’s good practice.

Generating the Certificate
#

A self-signed certificate was generated on media-01 using a small script saved to ~/gen-certs.sh:

 1#!/usr/bin/env bash
 2
 3set -e
 4
 5mkdir -p ~/.vnc
 6
 7openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-384 \
 8  -keyout ~/.vnc/x509_key.pem \
 9  -out ~/.vnc/x509_cert.pem \
10  -days 3650 -nodes \
11  -subj "/CN=media-01" \
12  -addext "subjectAltName=IP:192.168.2.124,IP:127.0.0.1,DNS:localhost" \
13  -addext "extendedKeyUsage=serverAuth" \
14  -addext "keyUsage=critical,digitalSignature,keyEncipherment" \
15  -addext "basicConstraints=critical,CA:FALSE"
16
17chmod 600 ~/.vnc/x509_key.pem
18chmod 644 ~/.vnc/x509_cert.pem
19
20echo "Certificate generated successfully:"
21openssl x509 -in ~/.vnc/x509_cert.pem -text -noout
1chmod +x ~/gen-certs.sh
2./gen-certs.sh

Client Certificate
#

Copy the server certificate to the client machine so the viewer can verify the server’s identity:

1scp box02@192.168.2.124:~/.vnc/x509_cert.pem ~/.vnc/x509_ca.pem

TigerVNC Client Config
#

At this point I just updated ~/.config/tigervnc/media-01-shared.tigervnc with the below contents to get the client working with TLS auth. It is a direct copy in its entirety:

 1TigerVNC Configuration file Version 1.0
 2
 3ServerName=127.0.0.1:5901
 4X509CA=/home/boringpc/.vnc/x509_ca.pem
 5SecurityTypes=X509Vnc
 6ReconnectOnError=1
 7Shared=0
 8AutoSelect=0
 9FullColor=1
10LowColorLevel=2
11PreferredEncoding=ZRLE
12CustomCompressLevel=0
13CompressLevel=6
14NoJPEG=1
15QualityLevel=8
16FullScreen=1
17FullScreenMode=Current
18FullScreenSelectedMonitors=1
19ViewOnly=0
20EmulateMiddleButton=0
21DotWhenNoCursor=0
22AcceptClipboard=1
23SendClipboard=1
24SendPrimary=1
25SetPrimary=1
26MenuKey=F8
27FullscreenSystemKeys=1
28desktopSize=1920x1080

Note: ServerName uses 127.0.0.1 instead of localhost to match the certificate SANs and avoid a hostname mismatch warning.


Security Considerations
#

  • Bind VNC to localhost only
  • Access via SSH tunnel
  • Avoid exposing ports 5901/5902
  • Consider firewall rules even on LAN
  • Use key-based SSH authentication
  • Use TLS (X509Vnc) for defense in depth

Troubleshooting
#

  • Black screen: Ensure dbus-x11 is installed.
  • Session exits immediately: Check ~/.vnc/xstartup permissions.
  • Permission denied: Verify chmod 600 ~/.vnc/passwd.
  • Wrong display: Confirm with echo $DISPLAY.
  • Wayland issues: Ensure you are running an X11 session if required.
  • Certificate hostname mismatch: Use 127.0.0.1 instead of localhost in the TigerVNC config.

Closing Thoughts
#

This setup has worked reliably for me and gives me both a persistent virtual session and live desktop control, all without exposing VNC directly to the network. Documenting it here ensures I can quickly reproduce it later.

I used Claude and ChatGPT as sounding boards while working through this setup. They were helpful for troubleshooting and reviewing the post, but the ideas, decisions, and final edits are mine.