HOWTO: Boot your Raspberry Pi into a fullscreen browser kiosk

It seems there’s some demand for knowledge of setting up a full-screen, browser-based kiosk on the all-singing Raspberry Pi. Here at Watershed we’ve done this, to drive the screens of our digital signage system. Although we complicate matters a bit (we net-boot the pis, which requires a few extra tweaks – see our blog post on net-booting raspberry pis), we thought it’d be useful to document what we achieved: automatically running Chromium, full-screen, with a single kiosk-mode web-page.

Step 1: Initial setup

For the most part, we’ve used a stock Raspbian raw image, updated to the latest versions; but we’ve then installed several additional packages:

  • matchbox
  • chromium
  • x11-xserver-utils
  • ttf-mscorefonts-installer
  • xwit
  • sqlite3
  • libnss3

To do this, run the commands:

sudo apt-get update

sudo apt-get dist-upgrade

sudo apt-get install matchbox chromium x11-xserver-utils ttf-mscorefonts-installer xwit sqlite3 libnss3

sudo reboot

Step 2: Automatic resolution detection

This was the hardest part for us because we have several monitors of slightly different native resolutions and, through net-booting, only wanted to have one filesystem-image presented to all the Pis. After a few days’ tinkering, I came up with this strategy: set the internal framebuffer to as large as it can be, then detect the monitor’s capabilities and adjust. If you know exactly what resolution your monitor is, just tweak the config.txt file and skip the rest of this step!

So, first set the framebuffer up by adding this to /boot/config.txt:

# 1900x1200 at 32bit depth, DMT mode








Next, add this to /etc/rc.local; it waits for a monitor to be attached to the HDMI socket, probes it for its preferred mode, sets that preferred mode and finally resets the framebuffer ready for X to takeover:

# Wait for the TV-screen to be turned on...

while ! $( tvservice --dumpedid /tmp/edid | fgrep -qv 'Nothing written!' ); do


	printf "===> Screen is not connected, off or in an unknown mode, waiting for it to become available...\n"

	sleep 10;


printf "===> Screen is on, extracting preferred mode...\n"


eval $( edidparser /tmp/edid | fgrep 'preferred mode' | tail -1 | sed -Ene 's/^.+(DMT|CEA) \(([0-9]+)\) ([0-9]+)x([0-9]+)[pi]? @.+/_GROUP=\1;_MODE=\2;_XRES=\3;_YRES=\4;/p' );

printf "===> Resetting screen to preferred mode: %s-%d (%dx%dx%d)...\n" $_GROUP $_MODE $_XRES $_YRES $_DEPTH

tvservice --explicit="$_GROUP $_MODE"

sleep 1;

printf "===> Resetting frame-buffer to %dx%dx%d...\n" $_XRES $_YRES $_DEPTH

fbset --all --geometry $_XRES $_YRES $_XRES $_YRES $_DEPTH -left 0 -right 0 -upper 0 -lower 0;

sleep 1;

Step 3: Launching Chromium

With that all done, the installation needs to be told to start-up X using a tailored xinitrc (kept on the boot-partition so that it can easily be edited on a non-Linux machine) by adding the following to /etc/rc.local:

if [ -f /boot/xinitrc ]; then

	ln -fs /boot/xinitrc /home/pi/.xinitrc;

	su - pi -c 'startx' &


And the xinitrc file looks like this:


while true; do

	# Clean up previously running apps, gracefully at first then harshly

	killall -TERM chromium 2>/dev/null;

	killall -TERM matchbox-window-manager 2>/dev/null;

	sleep 2;

	killall -9 chromium 2>/dev/null;

	killall -9 matchbox-window-manager 2>/dev/null;

	# Clean out existing profile information

	rm -rf /home/pi/.cache;

	rm -rf /home/pi/.config;

	rm -rf /home/pi/.pki;

	# Generate the bare minimum to keep Chromium happy!

	mkdir -p /home/pi/.config/chromium/Default

	sqlite3 /home/pi/.config/chromium/Default/Web\ Data "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR); INSERT INTO meta VALUES('version','46'); CREATE TABLE keywords (foo INTEGER);";

	# Disable DPMS / Screen blanking

	xset -dpms

	xset s off

	# Reset the framebuffer's colour-depth

	fbset -depth $( cat /sys/module/*fb*/parameters/fbdepth );

	# Hide the cursor (move it to the bottom-right, comment out if you want mouse interaction)

	xwit -root -warp $( cat /sys/module/*fb*/parameters/fbwidth ) $( cat /sys/module/*fb*/parameters/fbheight )

	# Start the window manager (remove "-use_cursor no" if you actually want mouse interaction)

	matchbox-window-manager -use_titlebar no -use_cursor no &

	# Start the browser (See

	chromium  --app=http://URL.of.your/choice.html


And that’s all there is to it; just (re)boot your Pi and it should boot, detect the screen and optimise for it, start X, launch Chromium and be ready with your chosen web-page in kiosk-mode!

Optional bonus Step 4: Accelerated X11 Server

Currently (Sep 2013), the default X11 server isn’t exactly optimised for the Raspberry Pi; however, work is afoot and you can install a work-in-progress version fairly easily. This has worked for us and has been nice and stable. YMMV.

UPDATE: Comments have suggested possible problems with newer Rasbian releases. We’ve not verified this either way. You don’t need to install this, everything will work just fine without it; at the time, we were trying to squeeze every last ounce of performance from the Pi, so it seemed pertinent to install this patch.

First, download xorg-server.tar.gz and xserver-xorg-video-fbdev.tar.gz from Simon J Hall’s latest release on github and extract them to the Pi:

sudo tar xfvz xorg-server.tar.gz -C /

sudo tar xfvz xserver-xorg-video-fbdev.tar.gz -C /

Next, edit /usr/share/X11/xorg.conf.d/01-fbdev-rpi.conf and change MboxFile to /dev/char_dev and finally, add the following to /etc/rc.local before X11 is launched (i.e. before the bit added in Step 3 above:

if [ ! -e /dev/dmaer_4k ]; then

	printf "===> Preparing to launch X...\n";

	modprobe dmaer_master

	major=$(awk '$2=="dmaer" {print $1}' /proc/devices)

	mknod /dev/dmaer_4k c $major 0

	mknod -m 664 /dev/char_dev c 100 0

	chgrp pi /dev/char_dev


Update: Console access

We’ve been asked how you might get access to the console to control the Pi if you don’t happen to have a network connection handy through which to SSH. The simple answer is to use one of the virtual consoles. Pressing Alt-F1 through Alt-F12 will switch you one of them. If X11 has already started, you’ll often need to press Ctrl-Alt-F1 to ‘break out’, and just Alt-F2 etc thereafter. X11 will take the next available slot, since the default Raspbian defines six consoles, this is usually at Alt-F7.

35 thoughts on “HOWTO: Boot your Raspberry Pi into a fullscreen browser kiosk

  1. Hi, thank you for your instructions!
    I tried this on my PI but get a lot of errors at startup and a black “page”.

    my output:
    [] Failed to load NSS libraries.
    [] Failed to create /home/pi/.config/chromium/SingletonLock: Keine Berechtigung
    [] Failed to create a ProcessSingleton for your profile directory. This means that running multiple instances would start multiple browser processes rather than opening a new window in the existing process. Aborting now to avoid profile corruption.
    rm: Entfernen von „/home/pi/.config/chromium/Default/Web Data“ nicht möglich: Keine Berechtigung
    Error: table meta already exists

    can you give me a hint, what i made wrong?

    • ok. figured it out 🙂
      my .config/chromium was created with root-priviliges …
      but is the message “Failed to load NSS libraries.” normal?

    • Hi Chriz,

      Glad you found the post helpful and also that you managed to resolve your permissions problem!

      As for NSS, I believe we’re using the libnss3 package; I thought this was part of the stock install, but perhaps not. You can verify its presence with “dpkg –status libnss3″, or install it with “sudo apt-get install libnss3″. I’ll update the post.

      I hope that helps and thanks for reading/commenting! 🙂


  2. very nice walk through,

    I am having an issue with it working though.

    The system boots up and near the end shows the display scripts as written above with the following errors:

    Powering on HDMI with explicit settings (CEA mode 4)
    [E] Failed to power on HDMI with explicit settings (CEA mode 4)

    But the display works fine and so im not worried about that but startx doesn’t seem to be being ran etc.

    It will then reset the frame buffer then it asks me to sign in as pi, I do then nothing.

    Am I missing something? Thank You

  3. Hi Bill,

    We developed the system with several displays of varing age and capability (whatever was around the office) and I do recall seeing that error on a couple of them, very early on in the project. However, the displays we’re using now, in “production” seem fine, so I’ve not probed it further. It’s also possible that we silently fixed the issue either with a different HDMI cable/adapter or with firmware/OS updates etc.

    I think it’s worth stating though that in the article, I assume a fresh installation of Raspbian, a 512MB-RAM Pi and a max resolution of 1900×1200 (set in /boot/config.txt); if any of that’s different, your mileage may vary 🙁

    As a possible work-around though: if you don’t *need* automatic setting and you know (or can determine using initial parts of /etc/rc.local above) the monitor’s native resolution, you could always hard-code the $_* variables in latter section of /etc/rc.local…


  4. Great walkthrough. It worked perfect for my application. The raspberry starts right to my webpage and it looks great. I do have one major issue…..I’m getting really good at doing this process over and over again because my sd card corrupts the system on power-off. I have to start at square one every time. This is nothing with your walkthrough, but a persistent issue. Do you have any recommendation on making the entire system read only? I’ve edited /etc/fstab to add ro to the parameters and it will not boot full read only.

    Does anyone else have the same issues? When using as a kiosk, there is no proper way to powerdown unless you ssh into the unit and command it. Pulling the cord corrupts my disk about 30% of the time. Frustrating!


    • Hello Tony,

      We used to experience that problem to; especially as we wanted to force a complete reboot every morning. Our solution was to mount the boot-partition read-only and load the rest of the system-root via NFS.

      However, you could just mount the root read-only too and use tmpfs entries in /etc/fstab for writeable areas such as /tmp.


  5. Hi!

    Thanks for your walkthrough! I got the “basic” version of the kiosk to work just fine, but when i upgraded to the “accelerated” one, my rc.local fails with FATAL: Module dmaer_master not found.

    I don’t have any files /dev/dma* on the pi. That said, i have a /lib/modules/3.6.11+/kernel/arch/arm/mach-bcm2708/dmaer_master.ko (if that’s any help — modules are a bit over my understanding).


    • Hi Robin,

      Did you add the supplemental section to /etc/rc.local? If you did, did you ensure you placed it before the block containing “su – pi -c ‘startx'”? Otherwise, please ensure you have the latest Raspbian OS installed.


      • Hi,

        I’m having the same issue… I already updated raspbian to the latest version, but it did not help 🙁
        The mentioned code section is in the correct place in the file as well…

        Do you have any advice?


        • Hello R. B.,

          It definitely sounds like the required kernel-module is not being loaded, so the diagnostic steps to take would be to run each command from the code block and look for errors. I.e. what does sudo modprobe dmaer_master output?. Does dmaer_master appear in the output of sudo lsmod?

          Also, did you build a fresh install, or did you update an existing install using sudo apt-get dist-upgrade?


          • Hello Stewart,

            thanks for your answer!

            That’s what I already did…. running “sudo modprobe dmaer_master” says “FATAL: Module dmaer_master not found.” … it also does not appear in lsmod.
            I tried installing the module manually but that was really difficult. As I got it compiled and installed finally, it told me that dmaer_module was incompatible.. 🙁

            What do you mean with a “fresh install” ? I downloaded the newest version of raspbian, as I was told in your tutorial. Afterwards I updated all the packages with
            sudo apt-get update
            sudo apt-get upgrade and
            sudo apt-get dist-upgrade

            So I guess it has to be the newest version…


          • I now tried to reinstall raspberry right from the beginning and found out, that the error message appears after I run “sudo apt-get dist-upgrade”.

            As I tried to “modprobe dmaer_master” before calling the dist-upgrade command, I did not get an error…so it seems to has to do something with an update…

          • Hi R.B.,

            Did you reboot your Pi after running sudo apt-get dist-upgrade? It’s probable you’re getting the ‘incompatible’ message due to a kernel/module ABI version mismatch. I’ve updated the blog-post to make that step mandatory.

            I’ll see if I can get some time to run through everything from scratch; just to check that nothing’s fundamentally changed. However, I would say that the “Bonus step 4” is more of “optional extra” — you might find that you’re just fine with the native drivers.


  6. Hello Stewart, great tutorial. It works exactly as expected.
    After doing the whole tutorial, I’ve made the rest of the configuration using SSH. My question is, in the absence of network, how could I stop the script before it runs or get to a command line to interact with the raspberry?

    • Hi Luciano,

      I was going to suggest that you should still have access to the Pi’s other virtual consoles; i.e. attaching a keyboard and pressing Ctrl-Alt-F2 should take you to the second one where you can log in. However, having just tried it, I noted that the ‘startx’ line in /etc/rc.local was hogging the foreground and prevented that from working. I’ve updated the blog post (adding a ‘&’ to the line) appropriately.

      Hope that helps…


  7. Hrm.. weird. if I leave the init runlevel at 2 then I get a command line login, if I change it to 5, I get the lightdm login screen. If I run startx from the command line, I get kiosk mode as prescribed. The only thing I can think is that there is an init script that runs ahead of local.rc that is subverting it. I’ve been looking all over the web to try to figure out how to force matchbox to run. I can change the config of lightdm to automatically start the DE as “user” but can’t see a way to change the primary DE to matchbox.

    • Hi Wayne,

      Did you accidenatlly enable the desktop environment using raspi-config? We left it at the default setting: “Console – Text console, requiring login”


  8. I am wondering… why did you have to do “while true; do” in xinitrc? And also why did you have to kill the applications when there will be no applications (chromium, matchbox) running after startup?

    • Hi Mohammad,

      Basically, the whole Chromium startup/initialisation is wrapped in a loop so that if anything goes wrong and it crashes, it will automatically re-launch. You have a valid point though, we could have left the kill- and cleaning-commands until after the application launch, it just turned out that, in our case, the script was written before NFS-booting was introduced and whilst we were still experimenting, so a clean environment wasn’t always the case.


  9. Hello Stewart,

    Thanks for your great instructions, I really appreciate it. It was very straight forward, even me as a new raspberry pi user, could get the result by following it. However, I have a minor issue in order to get it to work on a start up, that I was wondering maybe you could help me with that. After I created the xinitrc on my home directory and run the startx, I am able to get the browser to full screen, however, if I reboot the Pi , It wouldn’t be the same and will only boot to Raspian. I have to mention that, the only location that I created the xinitrc was my home directory, and I did not know whether it needs to be created elsewhere(like boot directory), as you mentioned in that “if” condition.
    Again, Thanks for your useful post.


    • Thanks Steward,
      I got it to work on startup. Instead of using
      “if [ -f /boot/xinitrc ]; then
      ln -fs /boot/xinitrc /home/pi/.xinitrc;
      su – pi -c ‘startx’ &

      I just added su – pi -c ‘startx’ to rc.local and restart the device and boom, everything look great. Thanks again.

      • Hi Shayan,

        Thanks. Glad you got it working. As I mention in the post, we keep the xinitrc file on the /boot partition and link to it before starting X up. So yes, in your case of an xinitrc file in your (user pi’s) home folder, the linking is unecessary and you can simply call the “su….” line.


  10. Hi Stewart,

    Thanks so much for this walk-through. It was my first time programming a raspberry – and really anything for that matter, and it work perfectly. One quick question – how to I get back to the rc.local file to alter the URL? I used a very simple URL while programming for ease sake – but now I need to change it to the production URL we want to use. I apologize for my lack of understanding on this platform, as I am sure it is just a simple keystroke, but any help would be much appreciated.

    Thanks so much once again!

    • Hi Eric,

      The only place the URL is stored is xinitrc, so just edit that and change the --app=... line.


      • Hello again,

        In case you were asking how to get to edit the xinitrc file, you have three choices:

        • SSH in using the Raspbian defaults (DHCP-allocated IP-address, user pi, password raspberry)
        • Power-down the Pi, transfer the SD-card to another machine and edit the xinitrc file there
        • Switch to a second virtual-console (Ctrl-Alt-F2), log-in with Raspbian defaults (user pi, password raspberry), edit /boot/xinitrc and restart Chromium (killall chromium)


  11. Thanks for this tutorial, it worked great for me. The only problem I am having now, is Chromium boots into kiosk mode fine, but it is only half screen. Any ideas?

    • Hi Calvin,

      If X11 is taking the entire screen, then I can only assume it’s a bug with Chromium or and/or the window manager (Matchbox). Otherwise, it sounds like the framebuffer mode isn’t quite right. You could check what it has been set to by running fbset (via SSH or a virtual terminal). If that’s wrong, you may need to hard-code the values in the section added to /etc/rc.local. Also, check that the expected resolution is around/below 1900×1200; if higher, you may need to tweak the values in /boot/config.txt.


      • Stewart,

        Before I read your response to my half screen chromium issue, I tried to optimize x11. Now all I get is a flashing cursor. I can get into other consoles, but I cannot start the GUI. When I try startX I get error 1 no screens found. I have tried to go in and revert the edited files to their previous states with “sudo vi” to no avail. Could this have something to do with the x11 files I unpackaged? Also, I do have it set to boot into the gui instead of the console, but before I tried the bonus accelerate x11 I had no boot issues.



  12. Alright, I have another very random question. I was able to get this set up and its working beautifully. The one issue I have is apparently with the wireless keyboards I purchased to be able to control my screens. (I dont have much control over my wireless network, my installation is on a University campus and central IT runs the network.) Wired and other full size wireless keyboards send ctrl+alt+f1 fine to break out of X11 and get to another console, but it does not recognize it from the mini wireless keyboard ( and it will also not register alt+fN to switch between consoles.

    Using showkey –scancodes seems to act the same way for both the mini keyboard as well as the wired, but it doesn’t act the same in practice. Is there anything I can do? I am attempting setkeycodes but having little success. It either doesn’t work or I don’t know what I’m doing. Entering:

    sudo setkeycodes 1d9d38b852d3 112

    Gives me: KDSETKEYCODE: Invalid arguement
    failed to set scancode7fff207f to keycode 112

    Any ideas, or am I up a creek with these keyboards?

    • Hi Calvin,

      I’m sorry to say that a) we’ve not used those keyboards nor b) have any idea why they’d act differently under X11, especially if emitting the same key-codes. Perhaps you’d have better luck asking on the Raspberry Pi forums?


  13. That Tutorial is great but chromium always shows the google-translate-bar. Is there any way to disable it?

    Best Regards


  14. I have a free and open source turn-key kiosk over at I’ve been thinking about porting it to the rpi. (I’ve found a few posts on getting the administration tool I use, Ajenti, on to the rpi so it should be possible.) If anyone would be interested in me pursuing a port please let me know in our forums. There are also links at the site to the code on GitHub.

Comments are closed.