TigerVNC on the shared box — Ops Runbook
Host sj4dl360n4u20 · OS Ubuntu 24.04.4 · Desktop Xfce 4.18 · VNC TigerVNC 1.13.1 · Portal xdg-desktop-portal 1.18.4 · Updated 2026-08-29
Isolated VNC desktops for several users on one Xubuntu server — the browser upload-dialog saga (resolved by 24.04), the in-place 22.04→24.04 upgrade, hardening, and password recovery.
1. Display map
| Display | Port | User | Service | Managed by |
|---|---|---|---|---|
:1 |
5901 | marslo (owner) | tigervncserver@:1 |
migrated off the custom unit (§6) |
:2 |
5902 | ubuntu (guest) | tigervncserver@:2 |
official mechanism |
:3 |
5903 | iliad (guest) | tigervncserver@:3 |
official mechanism |
Each display is a separate X server on its own port with its own ~/.vnc/passwd — users are isolated by construction; nobody enters another display without that display's password.
Why not just
vncserver@2? The old hand-rolled unit hardcodedUser=marslo, and@Nwas the display number — sovncserver@2/@3would have opened more desktops as marslo. Multi-user needs the official display→user map.
2. Enable VNC for another account
# /etc/tigervnc/vncserver.users — one line per display
sudo tee /etc/tigervnc/vncserver.users >/dev/null <<'EOF'
:1=marslo
:2=ubuntu
:3=iliad
EOF
# each user gets their OWN password (run as that user)
sudo -iu ubuntu vncpasswd
sudo -iu iliad vncpasswd
# start now + on boot (instance name includes the colon)
sudo systemctl enable --now tigervncserver@:2.service
sudo systemctl enable --now tigervncserver@:3.service
Each guest needs their own ~/.vnc (password + an xstartup for XFCE, options in ~/.vnc/tigervnc.conf).
3. Per-user config — ~/.vnc/tigervnc.conf
The tigervncserver@ unit reads options from this file (not the command line). Perl syntax, must end in 1;:
$geometry = "1920x1080";
$depth = "24";
$localhost = "no"; # listen on all interfaces; "yes" = localhost-only
1;
⚠️ With default
VncAuth, TigerVNC defaults to$localhost="yes"(localhost-only), unlike the old custom unit's-localhost no. Set$localhost="no"for direct network access, or leave it for SSH-tunnel-only (safer, §8).
4. The xstartup reference
#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
# runtime dir; avoids apps falling back to /tmp (PAM login also provides it now)
test -d "/run/user/$(id -u)" && export XDG_RUNTIME_DIR="/run/user/$(id -u)"
# desktop identity; ":GNOME" makes the portal pick the GTK FileChooser backend (UseIn=gnome)
export XDG_CURRENT_DESKTOP=XFCE:GNOME
# clipboard bridge between vnc viewer and X
vncconfig -nowin &
autocutsel -fork
autocutsel -selection PRIMARY -fork
# disable X screensaver / blanking
xset s off -dpms 2>/dev/null
# DPI has no tigervnc.conf knob — set it here
xrandr --dpi 120 2>/dev/null
exec dbus-run-session -- startxfce4
dbus-run-session— neitherstartxfce4nor the Xfcexinitrcstarts a session bus, andXDG_RUNTIME_DIRisn't inherited from the system service, so the systemd user bus isn't the default either. This guarantees a private bus and tears it down on logout.XDG_CURRENT_DESKTOP=XFCE:GNOME— the:GNOMElets the portal serve the file-chooser (§5). Drop toXFCEonly if you deliberately disable the portal.
5. Browser file uploads (Chrome & Firefox)
Chrome ≥123 and Firefox hand the file-picker to xdg-desktop-portal when it's reachable. How well it behaves depends almost entirely on the portal version — which is why the 24.04 upgrade mattered.
✅ Resolved on 24.04 — keep the portal ON. With
xdg-desktop-portal 1.18the dialog double-clicks to upload AND remembers the last folder, in both browsers. On 22.04's1.14you couldn't get both at once — the upgrade dissolved the trade-off.
| Portal version | Double-click uploads | Opens at |
|---|---|---|
| 1.18 — Ubuntu 24.04 (now) | yes | last-used folder |
| 1.14 — Ubuntu 22.04 | closed dialog without uploading | $HOME (never last dir) |
How "portal ON" is wired: packages xdg-desktop-portal + xdg-desktop-portal-gtk installed, no shadow file, and XDG_CURRENT_DESKTOP=XFCE:GNOME (the :GNOME makes the portal pick the GTK FileChooser backend — gtk.portal declares UseIn=gnome).
Fallback — turn the portal OFF (rarely needed on 24.04): shadow its D-Bus activation and set XDG_CURRENT_DESKTOP=XFCE:
mkdir -p ~/.local/share/dbus-1/services
tee ~/.local/share/dbus-1/services/org.freedesktop.portal.Desktop.service <<'EOF'
[D-BUS Service]
Name=org.freedesktop.portal.Desktop
Exec=/bin/false
EOF
# restart the session; delete the file to re-enable
⚠️
systemctl --user maskdoes NOT work here — the session runs on adbus-run-sessionbus with no systemd activation, so the portal is activated directly by dbus-daemon. Shadowing the D-Bus service file is the reliable, bus-agnostic lever.
Firefox — install the deb, not the snap. The snap fails in this VNC session with … is not a snap cgroup for tag snap.firefox.firefox. Use Mozilla's apt repo and pin out the snap so upgrades never switch back:
sudo install -d -m 0755 /etc/apt/keyrings
wget -q https://packages.mozilla.org/apt/repo-signing-key.gpg -O- \
| sudo tee /etc/apt/keyrings/packages.mozilla.org.asc > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" \
| sudo tee /etc/apt/sources.list.d/mozilla.list > /dev/null
# pin: Mozilla's firefox wins (1000), Ubuntu's snap-stub blocked (-1)
sudo tee /etc/apt/preferences.d/mozilla-firefox >/dev/null <<'EOF'
Package: firefox*
Pin: origin packages.mozilla.org
Pin-Priority: 1000
Package: firefox*
Pin: release o=Ubuntu
Pin-Priority: -1
EOF
sudo apt update; sudo snap remove firefox 2>/dev/null; sudo apt purge -y firefox; sudo apt install -y firefox
6. Retire the custom unit (done)
marslo's :1 was moved off /etc/systemd/system/vncserver@.service onto the official mechanism. Bonus — tigervncsession opens a real PAM login, so XDG_RUNTIME_DIR is populated for free.
- CLI options →
~/.vnc/tigervnc.conf(§3);-dpi 120→~/.vnc/xstartup(§4);xstartupcarries over.
# :1=marslo added to /etc/tigervnc/vncserver.users, then:
sudo systemctl disable --now vncserver@1.service
sudo systemctl enable --now tigervncserver@:1.service
The custom unit is disabled but not deleted (kept as a fallback). It survived the 24.04 upgrade and
tigervncserver@:1auto-starts on boot. Rollback:sudo systemctl disable --now tigervncserver@:1 && sudo systemctl enable --now vncserver@1.
7. The 22.04 → 24.04 upgrade
# ALWAYS in tmux/screen — a dropped SSH mid-upgrade can wreck the box
tmux new -s upgrade
sudo apt update && sudo apt full-upgrade -y && sudo apt --purge autoremove -y
sudo do-release-upgrade # opens a backup sshd on :1022; keep old configs (sshd_config!)
Disk was 88% full — grow the LV online (no unmount, no reboot, no data loss; only shrinking is risky):
sudo lvextend -L 200G -r /dev/ubuntu-vg/ubuntu-lv # -r also grows the ext4 fs
When the VG itself is full (VFree = 0): feed capacity from the bottom first — new disk →
pvcreate+vgextend; enlarged disk →growpart+pvresize; thenlvextend -r. Diagnose withvgs(VFree) /pvs(PFree); canonical LV path vialvs -o lv_path.
Third-party apt repos after the upgrade — do-release-upgrade disables them (*.list.distUpgrade); re-enable the ones you use for noble. Two bit us:
- ⚠️ Marvell Artifactory mirror serves an unsigned
Release(Origin: Artifactory; noInRelease/Release.gpg, no key endpoint), soSigned-Byis impossible. Either:- (a) keep the mirror — add
Trusted: yesto its deb822 stanza in/etc/apt/sources.list.d/third-party.sources(the leftoverN: Missing Signed-Bylines are harmless notices; only Artifactory-side signing removes them — an IT request), or - (b) switch to official signed sources — if the box reaches
archive.ubuntu.com, disablethird-party.sourcesand drop in a standardubuntu.sources(Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg): no warnings, real verification. Confirm first the mirror isn't mandated by network policy.
- (a) keep the mirror — add
- 🔒 Firefox — the upgrade reinstalls Ubuntu's snap-transition
firefox(Mozilla repo disabled). PressOk, finish, then re-enable Mozilla + apply the §5 pin.
8. Hardening
🔒
~/.vnc/passwdis reversibly obfuscated (fixed DES key), not hashed, and only the first 8 characters matter. Treat a VNC password as low-trust; never reuse it elsewhere.
Prefer localhost-only + SSH tunnel over open ports:
# set $localhost="yes" in tigervnc.conf, then tunnel (from the client)
ssh -L 5902:localhost:5902 ubuntu@sj4dl360n4u20
# then point the VNC viewer at localhost:5902
- Only SSH-authenticated users can reach the VNC port — the biggest single win.
- If you must expose ports (
$localhost="no"), firewall5901-5903to trusted IPs and use distinct passwords.
9. Forgot a VNC password
Just reset it — no old password needed:
vncpasswd
sudo systemctl restart tigervncserver@:1.service
Recover the old plaintext — it's fixed-key DES, so decryptable. Key {23,82,107,6,35,78,88,7} fed bit-reversed → E84AD660C4721AE0:
openssl enc -des-ecb -d -nopad -K E84AD660C4721AE0 \
-provider legacy -provider default \
-in ~/.vnc/passwd 2>/dev/null | head -c 8 | tr -d '\0'; echo
OpenSSL 3 (Ubuntu 22.04+) needs
-provider legacy -provider default; drop them on OpenSSL 1.1. A 16-byte file also holds a view-only password in bytes 9–16 (tail -c +9 | head -c 8).
10. Troubleshooting
"A Xtigervnc server is already running" (legacy custom unit) — systemd runs no shell, so the redirect in ExecStartPre=-/usr/bin/vncserver -kill :%i > /dev/null 2>&1 became literal args and the pre-kill always failed. Drop the redirect. If a restart still refuses:
vncserver -kill :1
rm -f /tmp/.X1-lock /tmp/.X11-unix/X1 ~/.vnc/*:1.pid # stale locks
Bus / env values differ between shells — expected:
| Context | DBUS_SESSION_BUS_ADDRESS |
XDG_RUNTIME_DIR |
DISPLAY |
|---|---|---|---|
| SSH login shell | unix:path=/run/user/1100/bus |
/run/user/1100 |
(empty) |
| Terminal inside VNC | unix:abstract=/tmp/dbus-… |
/run/user/1100 |
:1.0 |
The VNC session runs on its own dbus-run-session bus (a /tmp abstract socket); an SSH login uses the systemd user bus from pam_systemd. To poke the VNC session's bus from SSH: tr '\0' '\n' < /proc/$(pgrep -u marslo -x xfce4-session)/environ.