This is a follow-up to How to setup Chromium Kiosk on Ubuntu 26.04 in 30 seconds. That setup runs one X server with openbox, which already spans both monitors in a single shared framebuffer — you just need to (1) fix the monitor layout with xrandr and (2) launch the second program and move its window onto the second screen.
This post covers the single X server approach: both monitors are outputs on Screen 0 of DISPLAY=:0, sharing one clipboard, one X authority, and one coordinate space. Moving a window “to the second monitor” simply means positioning it at x ≥ 1920 in that shared space. If you want two fully independent X servers instead — each screen with its own DISPLAY, restarted and crashing independently — read why two separate X servers don’t work on a single GPU first: it requires two GPUs, since only one process can hold DRM master per card.
Step 1: Configure the monitor layout
Find the connector names of your two screens (run this once over SSH or a virtual console while the kiosk is up — if you get Authorization required, see How to check which monitors are connected on Linux):
sudo XAUTHORITY=/home/kiosk/.Xauthority DISPLAY=:0 xrandr --queryHDMI-1 connected primary 1920x1080+0+0 ...
HDMI-2 connected 1920x1080+1920+0 ...Edit ~kiosk/.config/openbox/autostart and add an xrandr line at the top so the layout is deterministic on every boot — never rely on auto-detection for a kiosk:
xrandr --output HDMI-1 --mode 1920x1080 --pos 0x0 --primary \
--output HDMI-2 --mode 1920x1080 --pos 1920x0HDMI-2 now sits to the right of HDMI-1, starting at X coordinate 1920.
Step 2: Pin the second program to the second screen via openbox
The clean way to do this is an openbox application rule. Since openbox is the window manager, it decides where new windows go — you just tell it that a given application always belongs on monitor 2.
The kiosk setup does not ship a per-user rc.xml, so create it at /home/kiosk/.config/openbox/rc.xml. This minimal file is all you need for the application rule:
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc">
<applications>
<application name="my-second-app">
<monitor>2</monitor>
<fullscreen>yes</fullscreen>
<decor>no</decor>
</application>
</applications>
</openbox_config>Make sure it’s owned by the kiosk user:
chown kiosk:kiosk /home/kiosk/.config/openbox/rc.xmlThe file must have the <openbox_config> root element — an <applications> fragment on its own is not a valid rc.xml. For all other settings (themes, keybindings, desktops) openbox falls back to its built-in defaults; see /etc/xdg/openbox/rc.xml as a reference for the additional options you can put in this file.
<monitor>2</monitor> tells openbox to always open matching windows on the second monitor, fullscreen, without window decorations. Match windows by name, class, or title — check the values by running xprop WM_CLASS and clicking the window.
Then add a second resilience loop to ~kiosk/.config/openbox/autostart:
while true; do
my-second-app
sleep 1
done &Note the trailing & — the existing while true; do chromium ... loop is blocking, so the second loop must run in the background. Chromium’s --kiosk mode already locks itself to the primary display, so nothing extra is needed for screen 1.
Alternative: move the window manually with wmctrl
Some programs — like mpv, which manages its own fullscreen — don’t play nicely with openbox placement rules. In that case, launch the program, wait for its window to appear, and move it to the second monitor’s coordinates yourself:
while true; do
mpv --fullscreen --loop=inf /opt/kiosk/loop.mp4 &
MPV_PID=$!
# Wait for the window to exist, then force it onto screen 2 (x >= 1920)
for i in $(seq 1 50); do
WID=$(xdotool search --onlyvisible --pid $MPV_PID 2>/dev/null | tail -1)
[ -n "$WID" ] && break
sleep 0.1
done
wmctrl -i -r "$WID" -e 0,1920,0,1920,1080
wmctrl -i -r "$WID" -b add,fullscreen
wait $MPV_PID
sleep 1
done &You’ll need xdotool and wmctrl:
sudo apt-get install -y xdotool wmctrlwmctrl -e 0,1920,0,1920,1080 moves and resizes the window to the rectangle starting at x=1920 — i.e. the second monitor.
Gotcha: xdotool search treats everything after the match criteria as a new command, so --onlyvisible must come before --class/--pid. xdotool search --class foo --onlyvisible fails with Unknown command: --onlyvisible and the wait loop times out with an empty WID.
Running a second Chromium on screen 2
A common kiosk setup is one Chromium per screen. Caveat: --kiosk Chromium ignores the openbox rc.xml monitor rule — it reasserts itself on the primary display, so the rc.xml approach above does not work for it. Use the wmctrl method instead, plus two extra flags so the second instance doesn’t collide with the first:
--class=chromium-screen2— sets a distinctWM_CLASSsoxdotoolcan find the right window--user-data-dir=/tmp/kiosk-screen2-profile— separate profile (Chromium refuses a second instance on the same profile)
while true; do
chromium \
--class=chromium-screen2 \
--user-data-dir=/tmp/kiosk-screen2-profile \
--kiosk \
--noerrdialogs \
--disable-infobars \
--no-first-run \
--disable-session-crashed-bubble \
--disable-component-update \
--check-for-update-interval=31536000 \
"https://techoverflow.net" &
APP_PID=$!
# Wait for the window to exist, then force it onto screen 2 (x >= 1920)
for i in $(seq 1 100); do
WID=$(xdotool search --onlyvisible --class chromium-screen2 2>/dev/null | tail -1)
[ -n "$WID" ] && break
sleep 0.1
done
# --kiosk already fullscreened it on monitor 1, so drop fullscreen,
# move, then re-fullscreen on the new position
wmctrl -i -r "$WID" -b remove,fullscreen
wmctrl -i -r "$WID" -e 0,1920,0,1920,1080
wmctrl -i -r "$WID" -b add,fullscreen
wait $APP_PID
sleep 1
done &This block must go before the blocking while true; do chromium ... loop for screen 1 in the autostart file (or anywhere, since it ends with &).
Restarting the kiosk
Changes to autostart or rc.xml take effect when openbox restarts:
sudo systemctl restart kiosk.serviceAlso see How to setup Chromium Kiosk on Ubuntu 26.04 in 30 seconds.