infrasdoc
Cloudflare · Zero Trust · Field notes

Headless Linux VNC over a public URL, via Cloudflare Zero Trust

Reach a private-network Linux desktop from a browser — no inbound ports, no client VPN. TigerVNC serves the desktop, a systemd service keeps it running across reboots, and a Cloudflare Zero Trust Access application renders the VNC session in the browser behind the App Launcher.

TigerVNC + XFCE systemd service Cloudflare ZTNA browser-based VNC port 5901 sanitized
00

What this builds

The end result is a Linux desktop you open from any browser through the Cloudflare App Launcher — the session is brokered by Cloudflare Access and tunnelled to the host's local VNC port, so nothing is exposed directly to the internet.

Assumes you already have

  • A Linux host with the XFCE desktop installed and sudo access.
  • A Cloudflare Zero Trust account with a tunnel / connector already installed on (or reaching) the host's network.
  • The host's private IP address — you'll point the tunnel at <host-ip>:5901 in Phase 3.
Sanitized

Example username is netadmin and the geometry/IP values are illustrative — substitute your own throughout.

1

Host machine setup (Linux)

1.1 — Install required toolsets

Update the package manager and install TigerVNC along with the required session controllers:

bash
sudo apt update
sudo apt install -y tigervnc-standalone-server tigervnc-common dbus-x11

1.2 — Configure VNC authentication

Run the password utility as the regular user account (e.g. netadmin). Do not run this as root or with sudo:

bash
vncpasswd
  • Set your access password when prompted (minimum 6 characters).
  • Type n when asked whether to configure a view-only password.

1.3 — Create the desktop startup script

Create or overwrite the environment profile script:

bash
nano ~/.config/tigervnc/xstartup

Paste the following exactly. It clears conflicting desktop variables and forces the native XFCE desktop to stay in the foreground:

~/.config/tigervnc/xstartup
#!/bin/sh
# Unset previous active desktop variables to avoid conflicts
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
# Grant permissions and define user runtime directories
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
install -d -m 700 "$XDG_RUNTIME_DIR"
# Read user interface resources if present
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
# Execute the local system configuration layers
xsetroot -solid grey
# Launch the desktop environment in the foreground
exec dbus-launch --exit-with-session startxfce4

Save and exit (Ctrl+O, Enter, Ctrl+X).

1.4 — Apply script permissions

Grant execute privileges to the profile script:

bash
chmod +x ~/.config/tigervnc/xstartup

1.5 — Adjust host firewall (if applicable)

If the host runs a local firewall, open port 5901 for the inner-network routing layer:

bash
sudo ufw allow 5901/tcp
Note

This only opens the port on the host's local firewall for the tunnel to reach it — the port is never published to the internet. Cloudflare brokers all external access.

2

System-wide automation (systemd)

2.1 — Create the global service file

So the VNC environment launches on boot without first logging into an SSH terminal, build a system-level process wrapper:

bash
sudo nano /etc/systemd/system/vncserver.service
Before pasting

If your username differs from netadmin, update the User, Group, WorkingDirectory, and Environment lines to match your exact home directory.

/etc/systemd/system/vncserver.service
[Unit]
Description=Remote desktop service (VNC) via Cloudflare Architecture
After=network.target network-online.target

[Service]
Type=simple
User=netadmin
Group=netadmin
WorkingDirectory=/home/netadmin
Environment=HOME=/home/netadmin
# Clean up stale pid/lock files from unexpected crashes/reboots
ExecStartPre=-/usr/bin/vncserver -kill :1
# Run VNC in the foreground so systemd can track the process safely
ExecStart=/usr/bin/vncserver :1 -fg -geometry 1920x1080 -depth 24 -localhost no

[Install]
WantedBy=multi-user.target

Save and exit (Ctrl+O, Enter, Ctrl+X).

2.2 — Activate the automation service

Reload the daemon configs, register the service to run on every boot, and start it now:

bash
# Reload the system daemon configs
sudo systemctl daemon-reload
# Enable it to run permanently on every boot
sudo systemctl enable vncserver.service
# Start the service immediately
sudo systemctl start vncserver.service

2.3 — Verification commands

PurposeCommand
live status & error handlingsudo systemctl status vncserver.service
restart manuallysudo systemctl restart vncserver.service
stop manuallysudo systemctl stop vncserver.service
verify binding portss -tulpn | grep 5901
Healthy

The binding-port check should show *:5901 or 0.0.0.0:5901 listening.

3

Cloudflare Zero Trust panel mapping

To expose this workspace cleanly through the public Cloudflare App Launcher, map the infrastructure variables in the Cloudflare Zero Trust dashboard as follows:

Workspace settingDashboard fieldSelection / destination
Tunnel configurationType / ProtocolSelect TCP
Tunnel configurationURL / Service destinationEnter <host-ip>:5901
Access applicationApplication typeSelect Self-hosted
Access applicationSession rendering toggleTurn ON "Allow access through browser-based RDP, SSH, or VNC sessions"
Access applicationSession protocol dropdownChoose VNC from the revealed selection
Access applicationApp Launcher visibilityTurn ON "Show in App Launcher"
Result

The desktop now appears as a tile in the Cloudflare App Launcher and opens the VNC session directly in the browser — gated by your Access policies.

Extending this

The -geometry 1920x1080 parameter in the service file sets the virtual screen size; a future revision will cover adjusting it dynamically for different monitors.