HomeRaspberry Pi › Webcam
Summary | Requirements | Hardware | Software | Daemon | Feedback | History

Raspberry Pi Webcam

How to turn your Raspberry Pi and its camera board into a constantly refreshing webcam with text overlay. Feedback welcomed at the Raspberry Pi Forum.



Install a web server and PHP including the php_gd extension. Use nohup to start raspistill in time-lapse mode. Create an index page that embeds the webcam image and a PHP script that generates the webcam image. Upon page request, grab the latest buffered image. Optionally overlay text before outputting.



Wireless adapters seem flaky on Linux; sometimes it is hard to get them to even work and in my experience they are often prone to drop the connection. My advice is to buy a Raspberry Pi model B with integrated USB-LAN adapter (ethernet port) and run a network cable to your modem or router. That way, the network connection will be stable and almost certainly a lot faster. Also, twice the amount of RAM in the Model B will not hurt wile having a camera attached and running a web server with PHP. Although I am sure this will work with a Model A.



Install the camera by following the official instructions. Be careful while handling bare circuit boards like the camera! Static electricity can easily ruin them and the flat cable and connectors are fairly fragile. Before turning on the computer, now is a good time to try and think of a clever way to fix the camera with a good view of its surroundings and maybe protect it from the elements and (further?) careless handling. Two 2 mm bolts will fit the holes. For my first try, I improvised by sticking one sturdy twist-tie through them, front to back, and wrapping the ends around a small stick.



Amendment to the official instructions: to get the latest firmware and camera code, don't just run apt-get upgrade, but also rpi-update:

sudo apt-get update
sudo apt-get dist-upgrade
sudo rpi-update
sudo reboot

In the latest Raspbian OS release, the rpi-update package is included. In an older release, should apt-get dist-upgrade not fetch it automatically, try sudo apt-get install rpi-update before running rpi-update.

After updating, make sure you enable the camera by running

sudo raspi-config

and pressing Enter on the appropriate option:


After confirmation and return to the main screen, press Tab to get to the Select / Finish options, right arrow to move to Finish, Enter, and confirm reboot. Test to see if everything works so far:

raspistill -o ~/Desktop/cam.jpg

which should turn the camera on (check the light), display a preview if you are running the graphical desktop, wait 5 seconds and save a photo called cam.jpg on your Desktop. To disable the camera light, for stealth operation, edit config.txt:

sudo nano /boot/config.txt

and add this on a new line: disable_camera_led=1. Save and exit by typing Ctrl-x, y, Enter. Next, install the lighttpd web server and PHP:

sudo apt-get install lighttpd php5-cgi php5-gd

Apache would also work but is more of a resource hog. Enable PHP:

sudo lighty-enable-mod fastcgi
sudo lighty-enable-mod fastcgi-php
sudo service lighttpd force-reload
echo '<?php phpinfo(); ?>' | sudo tee /var/www/phpinfo.php

and check to see if it works by pointing the Midori browser on your RPi to http://localhost/phpinfo.php. From another computer on the LAN, use the IP address of your Raspberry Pi (check by running ifconfig) or its hostname if your router understands that (probably requires installing avahi-daemon on the RPi). E.g. or http://raspberrypi/phpinfo.php. If your modem / router provides IPv6 connectivity, try enabling it on the RPi:

sudo modprobe ipv6
sudo nano /etc/modules   # add a new line with: ipv6

Normally, having an IPv6 address would immediately make your computer accessible from the web, without having to configure port forwarding on your modem / router. This is good and bad. Good, because no configuration and people may surf to your webcam. Bad, because hackers. Whenever your computer is visible from the outside, make sure to at least change your standard password! Use the password option in raspi-config or run passwd. Enable your router's IPv6 firewall but open port 80 for general web access or another port (1024 or higher) for semi-private access. Because this is a direct connection without any port translation (forwarding), if you use a port other than 80, reconfigure lighttpd to listen to that port.

Similar for the "normal" IPv4 connection: to make your webcam page accessible from the outside, configure port forwarding in the router from any address on the outside (all traffic for port 80 or otherwise 1024+) to port 80 on your RPi's LAN address. Change the outside port for semi-private access. If you change the inside port, you'll have to reconfigure your web server to listen to that port. Check your router manual for details :-). If that all went well, your RPi web server should be accessible to anyone surfing the web as http://your-public-internet-address:the-outside-port-you-opened/, e.g. (which is not a webcam, just the IP address of www.example.com and port 8080).

Now to get the webcam up and running. You need to create two files in the web server root directory (/var/www) and copy text to them from the two code blocks below. In general, I find the easiest way to do that is to open an SSH session to the RPi from my Mac, run an editor on the RPi: sudo nano /var/www/index.html (sudo is needed because the /var/www directory is owned by user root) and on the Mac copy-paste the text from the web browser to Terminal where the editor is running via SSH. In this case I also provided download links so you may do:

wget http://www.dronkert.net/rpi/webcam-html-1.0.0.txt
wget http://www.dronkert.net/rpi/webcam-php-1.0.3.txt
sudo cp webcam-html-1.0.0.txt /var/www/index.html
sudo cp webcam-php-1.0.3.txt /var/www/webcam.php

If wget is not installed on your RPi, run this first: sudo apt-get install wget.

Save this as /var/www/index.html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
Version : 1.0.0
Date : 2013-08-11
File : /var/www/index.html
Info : http://www.dronkert.net/rpi/webcam.html
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-gb" xml:lang="en-gb">
	<head profile="http://www.w3.org/2005/10/profile">
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>My Webcam</title>
		<meta name="keywords" content="raspberry pi, camera, webcam, raspistill, php, javascript" />
		<meta name="description" lang="en-gb" content="This is my webcam. There are many like it, but this one is mine." />
		<meta name="author" content="Ewoud Dronkert" />
		<style type="text/css">
			img#view { display: block; }
		<script type="text/javascript">
			var shotinterval = 5;     // Seconds between shots
			var shottimer = null;     // Countdown timer to the next shot
			var shootingnow = false;  // To avoid parallel execution

			// Start the timer
			function start() {
				shottimer = window.setTimeout(shoot, 1000 * shotinterval);

			// Stop the timer
			// Return previous state (was it running? true/false)
			function stop() {
				if (shottimer) {
					shottimer = null;
					return true;
				return false;

			// Stop the timer when running, start when not running
			// Avoid undefined state by checking for image refresh in progress
			function toggle() {
				if (!shootingnow) {
					var wasrunning = stop(), node;
					if (node = document.getElementById("ctrl"))
						node.innerHTML = wasrunning ? "Start" : "Stop";
					if (!wasrunning)

			// Refresh the webcam image by re-setting the src attribute
			function shoot() {
				shootingnow = true;
				var img; 
				if (img = document.getElementById("view"))  
					img.src = "webcam.php?" + Date.now();
				shootingnow = false;
	<body onload="start()">
		<img id="view" src="webcam.php" alt="[webcam]" title="Webcam" />
		<a id="ctrl" href="#" onclick="toggle()">Stop</a>

Save this as /var/www/webcam.php. It is very important to not have any blank lines or spaces before the first <?php or after the last ?>.


/* Version : 1.0.3
 * Date : 2013-08-14
 * File : /var/www/webcam.php
 * Info : http://www.dronkert.net/rpi/webcam.html

$now = time();     // Current time on the server
clearstatcache();  // To get fresh results for all file-info functions
$cam = '/dev/shm/webcam.jpg';  // Where raspistill will save every image
$buf = '/dev/shm/buffer.jpg';  // Buffer to avoid read/write collisions, also added text
$err = '/dev/shm/error.jpg';   // Error message if reading $cam or $buf failed

// Get file modification time of the camera image
$t_cam = 0;
if (file_exists($cam))
	if (is_readable($cam))
		$t_cam = filemtime($cam);

// Get file modification time of the image buffer
$t_buf = 0;
if (file_exists($buf))
	if (is_readable($buf))
		$t_buf = filemtime($buf);

// If camera image not found or not accessible
if (!$t_cam)
	$t_cam = $now;  // For the file name time-stamp

// JPEG image headers, time-stamped file name
header('Content-Type: image/jpeg');
header('Content-Disposition: inline; filename="webcam_' . date('Ymd_His', $t_cam) . '.jpg"');

// If the camera image is newer than the buffer and can be read
if ($t_cam > $t_buf && $im = @imagecreatefromjpeg($cam)) {

	// Save a new buffer
	$black = @imagecolorallocate($im, 0, 0, 0);
	$white = @imagecolorallocate($im, 255, 255, 255);
	$font = 1;  // Small font
	$text = date('r', $t_cam);  // ISO format date/time
	@imagestring($im, $font, 2, 1, $text, $black);  // Bottom-right shadow
	@imagestring($im, $font, 1, 0, $text, $white);
	@imagejpeg($im, $buf);  // Save to disk
	@imagejpeg($im);  // Output to browser (avoid another disk read)
	exit();  // End program early

// We are here if camera image not newer than buffer (or both not found)
// or reading the camera image failed

// If buffer not found or not accessible
// or reading+outputting the old buffer fails
// or reading+outputting existing error image fails
if (!$t_buf || !@readfile($buf) || !@readfile($err)) {

	// Create an error image
	$width  = 800;  // Should ideally be the same size as raspistill output
	$height = 600;

	if ($im = @imagecreatetruecolor($width, $height)) {

		$white = @imagecolorallocate($im, 255, 255, 255);
		$black = @imagecolorallocate($im, 0, 0, 0);
		@imagefilledrectangle($im, 0, 0, $width - 1, $height - 1, $white);  // Background
		@imagerectangle($im, 0, 0, $width - 1, $height - 1, $black);  // Border
		$font = 3;  // Larger font
		$text = 'Read Error';  // Message
		$tw = @imagefontwidth($font) * strlen($text);  // Text width
		$th = @imagefontheight($font);
		$x = (int)round(($width - $tw) / 2);  // Top-left text position for centred text
		$y = (int)round(($height - $th) / 2);
		@imagestring($im, $font, $x, $y, $text, $black);  // Write message in image
		@imagejpeg($im, $err);  // Save to disk
		@imagejpeg($im);  // Output to browser



The last piece of the puzzle is to have the raspistill tool continuously save camera pictures in the background. When I first tried to build this webcam, I let every new page request run raspistill for a single new image. That was a bad idea for a number of reasons, foremost the fact that concurrent invocations of raspistill apparently made the camera board or the video processor crash, requiring a reboot of the RPi. So I separated the two file transfer paths as much as possible. There is only one process accessing the camera and saving camera images to disk. The webcam.php script on the server polls if there is an image newer than the previously saved buffer, and if so, copies the latest camera image to the buffer. That should happen only once, no matter how many clients are viewing your webcam.

If reading the camera image or the buffer fails, the script falls back to the old buffer image or an error image. This means that sometimes you won't see the image refresh for a while. I have never seen it skip more than one time period, though.

Start the automated capture in a terminal window, at the console or via SSH:

nohup raspistill -n -e jpg -w 800 -h 600 -q 70 -t 2147483647 -tl 5000 -o /dev/shm/webcam.jpg &

where nohup lets the process continue even after you log off, -n prevents previews (I don't have a monitor plugged in), -t 2147483647 makes it run for almost 25 days (largest number possible in this version of raspistill), -tl 5000 has it take a picture every 5 seconds, -o /dev/shm/webcam.jpg saves the image on RAM disk and & makes it all run in the background. Press Enter again to get the prompt back. The console will look like this:

pi@raspberrypi ~ $ nohup raspistill -n -e jpg -w 800 -h 600 -q 70 -t 2147483647 -tl 5000 -o /dev/shm/webcam.jpg &
[1] 3027
pi@raspberrypi ~ $ nohup: ignoring input and appending output to ‘nohup.out’

pi@raspberrypi ~ $ 

When you get back on the console or start a new SSH session, you can check if the raspistill process is still going by running ps ux or, if for some reason you used sudo, ps aux. To stop it: killall raspistill.

Last tip: three things have to be synchronised, in order of importance:


Install as a Start-Up Daemon

Copy this or download it and save as /etc/init.d/webcam:

# Provides:		webcam
# Required-Start:	$remote_fs $syslog
# Required-Stop:	$remote_fs $syslog
# Default-Start:	2 3 4 5
# Default-Stop:		0 1 6
# Short-Description:	Control raspistill
# Description:		Control raspistill camera time lapse
#			and save images to RAM disk.

[ -x "$EXE" ] || exit 0

SCR="webcam"   # script / service name
ARG="-n -w 800 -h 600 -t 2147483647 -tl 5000 -o /run/shm/$SCR.jpg"
LCK="/var/run/$SCR.pid"   # lock file location

update_lockfile() {
	PID=$(pgrep "$BIN")
	if [ -z "$PID" ]; then
		[ -f "$LCK" ] && [ -w "$LCK" ] && rm -f "$LCK"
		[ -e "$LCK" ] || touch "$LCK"
		[ -f "$LCK" ] && [ -w "$LCK" ] && echo "$PID" > "$LCK"

do_start() {
	$EXE $ARG &

do_stop() {
	killall -q "$BIN"

case "$1" in
		echo -n "Service $SCR is "
		if [ -z "$PID" ]; then
			echo "not running." >&2
			exit 3
			echo "running."
		echo -n "Starting service $SCR..."
		if [ -n "$PID" ]; then
			echo "already started." >&2
			exit 4
			echo "done."
		echo -n "Stopping service $SCR..."
		if [ -z "$PID" ]; then
			echo "already stopped." >&2
			exit 5
			echo "done."
		echo -n "Restarting service $SCR..."
		if [ -n "$PID" ]; then
			echo "failed to stop." >&2
			exit 6
			if [ -z "$PID" ]; then
				echo "failed to start." >&2
				exit 7
				echo "done."
		echo "Usage: $0 {status|start|stop|restart|force-reload}" >&2
		exit 8
exit 0

Install as a service that will start at boot time:

sudo chmod 755 /etc/init.d/webcam
sudo update-rc.d webcam defaults 99 01
sudo reboot

Manual start/stop:

sudo service webcam start
sudo service webcam stop


Sorry, I will probably not respond to feedback. Try the Raspberry Pi Forum.


Version History

Updated init.d daemon service script. Source code: html | php | service
Added init.d daemon service script. Source code: html | php | service
Changed RAM disk reference from /run/shm to /dev/shm and size from 640x480 to 800x600. Source code: html | php
As suggested by marcelp1 on the Raspberry Pi Forum, don't write images to /tmp on the SD card but to /run/shm on the RAM disk. File location in raspistill command changed concordantly. Source code: html | php
Corrected shell command to create /var/www/phpinfo.php.
Some optimisations to webcam.php. Source code: html | php
Initial release. Source code: html | php