Turn any CLI-only Linux server into a remote desktop with a full GUI browser — accessible from any device on your local network.
No HDMI dummy plug needed. No physical display. Just Xvfb + x11vnc + noVNC + Chrome inside your browser.
┌─────────────────────────┐
│ Your Device (any OS) │
│ Chrome / Firefox │
└──────────┬──────────────┘
│ https://browser.lan
▼
┌─────────────────────────┐
│ Nginx (port 80/443) │
│ ┌──────────────────┐ │
│ │ Web UI │ │
│ │ (start/stop/ │ │
│ │ status/timer) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ /vnc/ │ │
│ │ (WebSocket proxy)│ │
└──────────┬──────────────┘
│ ws://localhost:6080
▼
┌─────────────────────────┐
│ websockify :6080 │
│ + noVNC HTML5 client │
└──────────┬──────────────┘
│ VNC RFB protocol
▼
┌─────────────────────────┐
│ x11vnc :5900 │
└──────────┬──────────────┘
│ X11
▼
┌─────────────────────────┐
│ Xvfb :99 (virtual │
│ framebuffer 1920x1080) │
│ + openbox + tint2 │
│ + Chrome/Chromium │
└─────────────────────────┘
- Start/Stop on demand — browser session only runs when you need it (saves RAM)
- Auto-idle timeout — session stops after 30 minutes of inactivity
- Full browser — Chrome/Chromium with address bar, DevTools, extensions
- Any device — works on laptop, tablet, phone (noVNC is HTML5)
- Auto-scale — remote desktop fills your browser window regardless of screen size
- Dark theme UI — native-feeling interface with status bar and progress timer
- Clipboard support — copy-paste between your device and the remote browser
| Component | Minimum |
|---|---|
| Server | Any Linux with x86_64 (Debian/Ubuntu recommended) |
| RAM | 2 GB (4 GB recommended for heavy browsing) |
| Disk | 5 GB free |
| OS | Debian 12 / Ubuntu 22.04+ |
| Dependencies | Xvfb, x11vnc, websockify, noVNC, openbox, tint2, Nginx, PHP-FPM, Chromium/Chrome |
git clone https://github.com/YOUR_USER/browser.lan.git
cd browser.lan
sudo bash install.shThis installs all dependencies, configures Nginx, sets up the systemd service, and deploys the web UI.
sudo DOMAIN=remote.myserver.local bash install.sh# Debian / Ubuntu
sudo apt-get update
sudo apt-get install -y xvfb x11vnc x11-utils openbox tint2 \
nginx php8.4-fpm novnc websockify chromium
# Or for Google Chrome:
wget -q -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt-get install -y /tmp/chrome.debsudo mkdir -p /var/www/html/vnc
sudo cp web/* /var/www/html/vnc/cp server/browser-session.sh ~/
chmod +x ~/browser-session.shsudo sed "s/YOUR_USER/$(whoami)/g; s|PATH_TO|$HOME|g" \
server/browser-session.service > /etc/systemd/system/browser-session.service
sudo systemctl daemon-reload
sudo systemctl enable --now browser-session.servicesudo cp web/nginx/browser.lan.conf /etc/nginx/sites-available/browser.lan
# Edit the config: replace DOMAIN, ALLOW_SUBNET, PHP_SOCK, WEB_ROOT
sudo ln -s /etc/nginx/sites-available/browser.lan /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginxAdd to /etc/hosts on your local machine:
192.168.1.2 browser.lan
Or to your router's DNS / dnsmasq:
address=/browser.lan/192.168.1.2
echo "www-data ALL=(root) NOPASSWD: /bin/systemctl start browser-session.service" | sudo tee /etc/sudoers.d/browser-lan
echo "www-data ALL=(root) NOPASSWD: /bin/systemctl stop browser-session.service" | sudo tee -a /etc/sudoers.d/browser-lan- Open
http://browser.lan(or your domain) - Click Start Session
- Wait ~15 seconds for the browser to launch
- Use Chrome remotely from any device
- Session auto-stops after 30 minutes idle, or click Stop
browser.lan/
├── install.sh # Automated installer
├── server/
│ ├── browser-session.sh # Main launcher (Xvfb + x11vnc + websockify + Chrome)
│ ├── browser-session.service # systemd service unit
│ ├── vnc-idle.service # Idle check oneshot service
│ ├── vnc-idle.timer # Idle check timer (every 1 min)
│ └── idle-check.sh # Auto-stop idle session script
├── web/
│ ├── index.html # Main web UI (start/stop/timer)
│ ├── vnc.php # Start/stop API
│ ├── status.php # Session status API
│ ├── heartbeat.php # Heartbeat API (activity tracking)
│ ├── vnc-offline.html # Offline/error fallback page
│ └── nginx/
│ └── browser.lan.conf # Nginx config template
└── README.md
By default the setup uses allow/deny on Nginx to restrict access to your local subnet. For remote access:
- Use Tailscale or Cloudflare Tunnel instead of exposing the server directly
- Or add HTTPS with Let's Encrypt and strong authentication
- The websockify proxy does not have authentication — keep it behind the Nginx IP restriction
| Problem | Fix |
|---|---|
| noVNC shows "Connection refused" | Ensure browser-session.service is running |
| White page in noVNC | Wait ~10s for Chrome to start |
| Canvas too small | The UI passes resize=scale to noVNC automatically |
| Keyboard not working in fullscreen | Safari limitation; use Chrome/Firefox |
| Port 6080 already in use | Change WEB_PORT in browser-session.sh |
- Xvfb creates a virtual display without physical hardware
- x11vnc serves the X display over VNC
- websockify converts WebSocket to raw TCP VNC
- noVNC is the HTML5 VNC client served by websockify
- openbox + tint2 provide a minimal window manager + taskbar
- The Web UI uses navigator.sendBeacon for activity heartbeat
- 30-minute idle timer auto-stops the session via systemd
MIT