A collection of scripts that can be used to quickly enable a raspberry pi to boot up as a stand alone kiosk. If you're wanting to run a local server, you will need to follow all of these steps. If you want to boot up and hit a public url, you can skip the Node install. The benefit of running your app locally is you can have a custom error screen if the internet connection drops out.
This guide uses Raspberry Pi OS Bookworm on a Raspberry Pi 4 or 5, with Node 24 LTS, which was LTS at the time of writing.
- A Raspberry Pi 4 or 5
- A microSD card, 8GB or larger, and a way to write to it from your computer
- A display, I recommend the official Raspberry Pi 7" touchscreen as it works out of the box
- A power supply for your Pi
- A computer running Raspberry Pi Imager, to flash the SD card
- Optional: a keyboard and mouse, but you can do the whole setup over SSH instead
- Flash the SD card with Raspberry Pi Imager
- Headless setup over SSH
- Clone the repo to the Pi
- Install Node
- Run your Node server on startup
- Setting up kiosk mode
- Display config
- Auto-reconnect Wi-Fi
- Turn the display on and off on a schedule
- Reboot the Pi into the kiosk
- Create your kiosk interface
Note
You can do everything in this guide without a keyboard or mouse attached to the Pi, only the display needs to be plugged in. If you want to do it this way, enable SSH and make sure the Pi has an internet connection. Either enable Wi-Fi and set it up when using the Raspberry Pi imager, or connect the Pi via ethernet.
Download Raspberry Pi Imager (https://www.raspberrypi.com/software/), which formats the SD card, writes the image, and pre-configures Wi-Fi, SSH, hostname, and locale in one step.
- Insert your SD card and open Raspberry Pi Imager
- Device: Choose your Pi model, for example, Raspberry Pi 5
- OS: Choose Raspberry Pi OS (64-bit)
- Storage: Choose your SD card
- Hostname: Enter a hostname, or leave it as
pi - Localisation: Choose your capital city, timezone, and keyboard layout
- Username: Enter a username, or leave it as
pi - Password: Enter and confirm a password
- Wi-fi: If you want the Pi to auto-connect to WIFI
- SSID: Enter your network name
- Password: Enter and confirm your network password
- SSH: If you want to set up the Pi using another device
- SSH: Enable
- Choose with password authentication
- Raspberry Pi Connect: If you want to control your Pi over the internet
- Pi Connect: Enable
- Write the image
- Put the card in the Pi and boot it up
Note
If you're using an attached mouse and keyboard, you can skip this step.
Open the terminal on a device connected to the same network, and run the following SSH command using the hostname you set in the imager.
ssh pi@raspberrypi.localOnce connected, run the following two commands to make sure the Pi and all of its packages are up to date.
sudo apt update
sudo apt full-upgrade -yNext, configure the Pi to boot straight to the desktop and auto-login. Otherwise your kiosk will hit a login screen and ruin the whole experience.
sudo raspi-config nonint do_boot_behaviour B4If you have any issue with the time being out of sync, you might want to also set the timezone in the config. I've had to do it on older Pi models, so I always just do it now, even though it's probably not necessary. Obviously you'd use the correct timezone for your location.
sudo raspi-config nonint do_change_timezone Europe/LondonIf you continue to have time issues, make sure the Pi is syncing over the internet.
Check the sync status:
timedatectlLook for the following in the output:
System clock synchronized: yes
NTP service: active
If it says no / inactive enable system-stymesyncd with the following commands:
sudo timedatectl set-ntp true
sudo systemctl enable --now systemd-timesyncdThen force an immediate resync and check it's now working:
sudo systemctl restart systemd-timesyncd
timedatectl timesync-statusYou shouldn't need to touch the display backend. A Pi 4/5 Bookworm image boots labwc (Wayland) by default, which is what the kiosk steps below assume. So, again this should not be necessary, but if you hit display issues you can try setting it to W2 manually.
sudo raspi-config nonint do_wayland W2Confirm what's set by checking the session. You should see something like:
autologin-session=rpd-labwc -> labwc is active
grep -v '^#' /etc/lightdm/lightdm.conf | grep -i sessionClone / download this repo onto the Pi, you can always delete it later but it just makes installing the scripts much easier. Make sure you put it at the following path: /home/pi/raspberry-pi-kiosk
The easiest way to do it is just to clone it with the following command:
git clone https://github.com/abbott567/raspberry-pi-kiosk.git /home/pi/raspberry-pi-kioskCD into the folder once it's cloned.
cd /home/pi/raspberry-pi-kioskNote
You can skip this step if you plan to just point the kiosk at a public URL.
Raspberry Pi OS ships an older Node. Install the current LTS from NodeSource.
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejsVerify it installed by checking the version.
node -vNote
You can skip this step if you plan to just point the kiosk at a public URL.
This repo ships a tiny, zero-dependency demo server in demo/ so you have something to display to test it's working.
The demo just renders a full-screen clock on http://localhost:3000. It's a way to verify everything is working, and can be a starting point for when you build your own kiosk app.
The server also exposes a tiny /health endpoint that the page polls to detect when the server or network drops out. This is the hook for the "custom error screen" mentioned at the top — poll /health from your own kiosk page and show whatever offline message you like when it stops responding.
Install the systemd service from this repo. It ships pointing at the demo above, and it runs as the unprivileged pi user, rather than root. It also restarts automatically if it crashes. If you changed any of these in your setup, then you'll need to edit the node-server.service file before running the following commands.
sudo cp /home/pi/raspberry-pi-kiosk/node-server.service /etc/systemd/system/node-server.service
sudo systemctl enable --now node-serverCheck the server is running.
systemctl status node-server
curl http://localhost:3000You should get back the HTML from the index.html page.
The service also reads /etc/kiosk.conf if it exists (via EnvironmentFile), sharing HEALTH_URL, MAX_FAILURES, FAIL_FILE, and HEALTH_TIMEOUT with the Wi-Fi watchdog from Step 8. It's optional — the server falls back to sensible defaults without it — so you can install it now or when you reach Step 8. If you add or change it later, run sudo systemctl restart node-server to pick it up.
Clone it onto the Pi. Then, edit the service file to match. You'll need to update the WorkingDirectory, ExecStart, and the port
Edit the file using nano.
sudo nano /etc/systemd/system/node-server.serviceThen reload the server.
sudo systemctl daemon-reload && sudo systemctl restart node-serverA Pi 4/5 Bookworm image boots the labwc compositor by default, which is what we target with the following two files:
kiosk.shlaunches Chromium with the kiosk flags. For the demo it's already pointed athttp://localhost:3000, if you want to point it at a different URL, edit it at the top of this fileautostarttells the Pi where thekiosk.shfile is on login, if you move the file you'll need to update it here
Make the script executable and wire it into labwc's autostart:
chmod +x /home/pi/raspberry-pi-kiosk/kiosk.sh
mkdir -p ~/.config/labwc
echo '/home/pi/raspberry-pi-kiosk/kiosk.sh &' >> ~/.config/labwc/autostartNotes on kiosk.sh:
--incognitostops the "restore pages?" Chromium prompt if the Pi isn't shut down properly--password-store=basicstops the "choose a password for the new keyring" GNOME prompt on the first boot
Note
You can skip this step if you're using an official Raspberry Pi display.
Official Raspberry Pi 7" touchscreen: this is what I use, and is auto-detected on Bookworm. You don't need to touch config.txt at all if you use the official display. If you need to rotate it, use the desktop Screen Configuration tool.
HDMI panels only: if you're using a fixed-resolution HDMI screen that won't show at the right resolution, there are two ways to fix it. Bookworm uses the KMS display driver by default, and most hdmi_* options in config.txt are ignored under it, so try these in order:
Set the resolution from the desktop Screen Configuration tool, or add a mode line to /boot/firmware/cmdline.txt:
sudo nano /boot/firmware/cmdline.txtAppend the following to the existing single line, adjusted for your display:
video=HDMI-A-1:800x480@60
If that doesn't work, older HDMI panels may still respond to the hdmi_* settings in config.txt. These are often ignored under KMS, so only try this if you're out of options:
Copy and adjust the settings in config.txt to /boot/firmware/config.txt, using nano:
sudo nano /boot/firmware/config.txtWi-Fi can be flaky on the Pi. The check-wifi.sh script checks the internet every minute by requesting HEALTH_URL, and if it detects it's down, reconnects the Wi-Fi via nmcli (NetworkManager). If the reconnect fails 5 times in a row, the Pi reboots itself as a last resort.
The number of retries (MAX_FAILURES) and the URL it checks (HEALTH_URL) live in kiosk.conf. The kiosk server reads the same file (see Step 5), so both use the same definition of "online" which keeps the offline overlay's X/5 countdown in sync with the reboot logic. Use the same HEALTH_URL here as the server, or the page can show "offline" while the watchdog still thinks the link is up.
Install the shared config, then the check-wifi.sh script, and make the script executable:
sudo cp kiosk.conf /etc/kiosk.conf
sudo cp check-wifi.sh /usr/local/bin/check-wifi.sh
sudo chmod 755 /usr/local/bin/check-wifi.shThen add the cron job to make it run every minute. Run the following command:
crontab -eThen paste in the line from the crontab file in this repo at the bottom:
*/1 * * * * /usr/bin/sudo -H /usr/local/bin/check-wifi.sh > /dev/null 2>&1
Note
Optional. Skip this step if you want the display on all the time.
If your kiosk doesn't need to be on 24/7, you can blank the screen overnight and wake it back up in the morning. The display.sh script turns the display off or on, and you schedule it with cron just like the Wi-Fi check.
Pi OS runs the labwc compositor, which is wlroots-based, so the script uses wlr-randr to power the output down and back up. This properly turns the panel off, rather than just showing a black page, so the backlight on the official touchscreen switches off too!
Install wlr-randr if it isn't already present:
sudo apt install -y wlr-randrInstall the display.sh script and make it executable:
sudo cp display.sh /usr/local/bin/display.sh
sudo chmod 755 /usr/local/bin/display.shYou can test it straight away from an SSH session:
/usr/local/bin/display.sh off
/usr/local/bin/display.sh onThen add the cron jobs. Run the following command:
crontab -eThen paste in the two display lines from the crontab file in this repo at the bottom, adjusting the times to suit. The example turns the screen off at 23:59 and back on at 07:00:
59 23 * * * /usr/local/bin/display.sh off > /dev/null 2>&1
0 7 * * * /usr/local/bin/display.sh on > /dev/null 2>&1
Notes:
-
The cron job runs as the
piuser, and the script pointswlr-randrat that user's session (/run/user/1000). If you set up the Pi with a different username, edit the UID at the top ofdisplay.sh— runid -u <user>to find it. -
The script auto-detects the Wayland socket, it can be
wayland-0,wayland-1etc, depending on the setup, so you shouldn't need to set it manually. It also skips the placeholderNOOPoutput thatwlrootscreates while the real display is off, so the screen wakes back up correctly.
With everything set up, reboot and the Pi should come back up straight into your kiosk:
sudo rebootNow that it's all up and running, you can just build your interface in regular old HTML, CSS and JavaScript.
You can hide the cursor using CSS.
To hide it completely, which is ideal for a touchscreen, use the following CSS:
html, body { cursor: none; }Note: CSS only hides the cursor over your page, and only once labwc has sent the browser a pointer event. On a touchscreen with no mouse, labwc still draws its own arrow in the centre of the screen at boot, before the first touch, and CSS can't clear that. To hide the compositor cursor too, let swayidle fire labwc's built-in HideCursor keybind.
Install the tools:
sudo apt install swayidle wtypeThen, and add this to ~/.config/labwc/autostart, unless you're using this repo's auto-start, as it's already included: autostart:
swayidle -w timeout 1 'wtype -M logo -M alt -k h -m alt -m logo' &The cursor then disappears ~1s after boot and re-hides shortly after each touch.
Or, if you want to show it, but hide it after a few seconds of inactivity, use the following CSS and JavaScript:
body.idle { cursor: none; }// 3 Seconds
const LIMIT = 3000;
let t;
const wake = () => {
document.body.classList.remove('idle');
clearTimeout(t);
t = setTimeout(() => document.body.classList.add('idle'), LIMIT);
};
['mousemove', 'mousedown', 'touchstart', 'keydown'].forEach(e =>
window.addEventListener(e, wake, { passive: true })
);
wake();