AlmaLinux / RHEL 8+ — Single shared session via x11vnc on a dedicated virtual display
xinit (~/.xinitrc + Xvfb :10) → x11vnc (port 5910)
└── openbox-session (looping)
└── your-app &- Xvfb — virtual framebuffer (no physical display required)
- xinit — starts Xvfb and executes
~/.xinitrcin one shot - Openbox — window manager, loops on exit to keep session alive
- x11vnc — shares the Xvfb display over VNC
- Port convention — display
:10maps to port5910(5900 + 10)
sudo dnf install -y epel-release
sudo dnf install -y x11vnc xorg-x11-server-Xvfb openbox xterm
xtermprevents Openbox pipe-menu errors on minimal installs. Alternatively, suppress the error with a custommenu.xml(see section 6).
Use a real user account (not a system account) so ~/.xinitrc and home directory work correctly:
sudo useradd -m vncuser
sudo passwd vncuser#!/bin/bash
export HOME=/home/vncuser
# xinit starts Xvfb :10 and executes ~/.xinitrc
xinit -- /usr/bin/Xvfb :10 -screen 0 1920x1080x24 -ac &
# Wait for display to be ready
sleep 1
export DISPLAY=:10
# x11vnc in foreground — keeps script alive
# When x11vnc exits, systemd restarts the whole stack
exec x11vnc \
-display :10 \
-forever \
-shared \
-nopw \
-rfbport 5910 \
-noxdamage \
-repeat \
-noipv6sudo chown vncuser:vncuser /home/vncuser/start-session.sh
chmod +x /home/vncuser/start-session.shKey flags:
| Flag | Purpose |
|---|---|
-forever |
Keep running after last client disconnects |
-shared |
All clients see the same session simultaneously |
-nopw |
No password (remove if auth is needed) |
-noxdamage |
Avoids rendering glitches with Xvfb |
#!/bin/bash
# Launch your app
/path/to/your-app &
# Example: Launch xterm
xterm &
# Loop openbox — if user exits the session it restarts
# This keeps xinit alive and the VNC session persistent
while true; do
openbox-session
sleep 1
donechmod +x /home/vncuser/.xinitrcCritical:
openbox-sessionmust loop. If it exits without a loop,xinitexits, x11vnc loses its display, and the VNC session dies.
[Unit]
Description=vncuser VNC Session
After=network.target
[Service]
Type=simple
User=vncuser
ExecStart=/home/vncuser/start-session.sh
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable vncuser-session
sudo systemctl start vncuser-sessionOn minimal installs without terminal emulators, create a static menu:
mkdir -p /home/vncuser/.config/openbox/home/vncuser/.config/openbox/menu.xml:
<?xml version="1.0" encoding="UTF-8"?>
<openbox_menu xmlns="http://openbox.org/3.4/menu">
<menu id="root-menu" label="Menu">
<item label="Reconfigure">
<action name="Reconfigure"/>
</item>
<item label="Exit">
<action name="Exit"/>
</item>
</menu>
</openbox_menu>chown -R vncuser:vncuser /home/vncuser/.config# Set default target to text mode
sudo systemctl set-default multi-user.target
# Disable GDM (if installed)
sudo systemctl disable gdm.service
sudo systemctl stop gdm.service
# Verify
systemctl get-default
# -> multi-user.targetThe vSphere/KVM console will show a plain
login:prompt. The VNC session runs fully headless and independently.
# Open port 5910
sudo firewall-cmd --permanent --add-port=5910/tcp
sudo firewall-cmd --reload
# Allow custom VNC port in SELinux (Might not be need in AlmaLinux/RHEL)
sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t vnc_port_t -p tcp 5910# Service status
systemctl status vncuser-session
# Confirm x11vnc is listening
ss -tlnp 'src = :5910'
# Live logs
journalctl -u vncuser-session -fDirect (same network):
<server-ip>:5910
Via SSH tunnel (NAT/KVM):
ssh -L 5910:<vm-ip>:5910 user@<vm-ip> -N &
# Then connect VNC client to localhost:5910| File | Owner | Purpose |
|---|---|---|
/home/vncuser/start-session.sh |
vncuser |
Starts xinit + x11vnc |
/home/vncuser/.xinitrc |
vncuser |
Defines X session (WM + app) |
/home/vncuser/.config/openbox/menu.xml |
vncuser |
Static Openbox menu |
/etc/systemd/system/vncuser-session.service |
root |
Systemd unit |
# Create VM
virt-install \
--name almalinux-vnc-test \
--ram 2048 --vcpus 2 \
--disk path=/var/lib/libvirt/images/almalinux-vnc-test.qcow2,size=20 \
--os-variant almalinux9 \
--cdrom ~/Downloads/almalinux9-minimal.iso \
--network network=default \
--graphics spice --video qxl \
--console pty,target_type=serial
# Get VM IP
virsh domifaddr almalinux-vnc-test
# Simulate vSphere text console
virsh console almalinux-vnc-test # Ctrl+] to exit
# SSH tunnel to reach x11vnc
ssh -L 5910:<vm-ip>:5910 vncuser@<vm-ip> -N &
vncviewer localhost:5910| Symptom | Fix |
|---|---|
x11vnc can't connect to display |
Increase sleep 1 in script to sleep 2 |
| Openbox pipe-menu error | Install xterm or add custom menu.xml |
| VNC session dies when user exits Openbox | Ensure openbox-session is in a while true loop in .xinitrc |
| Port 5910 refused | Check firewall-cmd and semanage port |
| SELinux denials | Run ausearch -m avc -ts recent and use audit2allow |
| Black screen on connect | Check journalctl -u vncuser-session for Xvfb/xinit errors |
/etc/gdm3/custom.conf
[daemon]
AutomaticLoginEnable=False
/etc/dconf/profile/user
user-db:user
system-db:local
/etc/dconf/db/local.d/00-kiosk
[org/gnome/desktop/lockdown]
disable-command-line=true
disable-user-switching=true
disable-application-handlers=true
[org/gnome/desktop/screensaver]
lock-enabled=false
idle-activation-enabled=false
[org/gnome/shell]
enabled-extensions=['just-perfection-desktop@just-perfection']
favorite-apps=['']
[org/gnome/shell/extensions/just-perfection]
panel=false
activities-button=false
app-menu=false
search=false
workspace=false
panel-notification-icon=false
[org/gnome/shell/keybindings]
toggle-overview=[]
toggle-application-view=[]
open-new-window=[]
switch-to-application-1=[]
switch-to-application-2=[]
switch-to-application-3=[]
switch-to-application-4=[]
[org/gnome/desktop/wm/keybindings]
panel-run-dialog=[]
switch-to-workspace-left=[]
switch-to-workspace-right=[]
panel-main-menu=[]
[org/gnome/mutter]
dynamic-workspaces=false
[org/gnome/desktop/interface]
enable-hot-corners=false
[org/gnome/desktop/background]
show-desktop-icons=false
[org/gnome/settings-daemon/plugins/media-keys]
custom-keybindings=['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ck0/']
[org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ck0]
binding='d'
command='gnome-control-center display'
name='Display Settings'
/etc/dconf/db/local.d/locks/00-kiosk-locks
/org/gnome/desktop/lockdown/disable-command-line
/org/gnome/desktop/lockdown/disable-user-switching
/org/gnome/desktop/lockdown/disable-application-handlers
/org/gnome/shell/enabled-extensions
/org/gnome/desktop/interface/enable-hot-corners
/org/gnome/desktop/background/show-desktop-icons
/org/gnome/desktop/app-folders/
/etc/xdg/autostart/kiosk-browser.desktop
[Desktop Entry]
Type=Application
Name=Kiosk Browser
Exec=/usr/local/bin/kiosk-browser.sh
X-GNOME-Autostart-enabled=true
/usr/local/bin/kiosk-browser.sh
#!/bin/bash
while true; do
chromium-browser --kiosk
--no-first-run
--disable-translate
--disable-infobars
--disable-session-crashed-bubble
--disable-pinch
--overscroll-history-navigation=0
https://your-url.com
sleep 2
done
/etc/profile.d/kiosk-lockdown.sh
#!/bin/bash
rm -rf "$HOME/.config/autostart"
mkdir -p "$HOME/.config/autostart"
chattr +i "$HOME/.config/autostart"
mkdir -p "$HOME/.config/dconf"
chattr +i "$HOME/.config/dconf"
SETUP COMMANDS
sudo chmod +x /usr/local/bin/kiosk-browser.sh
sudo chmod +x /etc/profile.d/kiosk-lockdown.sh
sudo dconf update
sudo systemctl mask getty@tty1 getty@tty2 getty@tty3
getty@tty4 getty@tty5 getty@tty6