(A rather secure) Raspberry Pi Puppy Cam

My girlfriend recently got a puppy (Fig 2), so I decided to build a puppy cam (Fig 1) for her/us 🙂 I already had a spare Raspberry Pi with all the needed hardware laying around.


Fig 1. Raspberry Pi with Logitech QuickCam Fusion


Fig 2. The camera victim (Flat-Coated Retriever)



  • Raspberry Pi Model B
  • Clear Raspberry Pi Case from www.modmypi.com
  • 16GB SD card
  • Logitech QuickCam Fusion (old crap capable of 640×480)
  • D-Link DWA-121 802.11n Wireless N 150 Pico Wi-Fi-adapter
  • Deltaco AC adapter, 230V – 5V, 1A, Micro USB, 1.8m
  • Raspbian (Wheezy), Release 2014-01-07
  • (for setup: HDMI-to-DVI adapter, usb hub, usb mouse + keyboard)



  • Installed Raspbian on a 16GB SD-card following the guide from https://www.andrewmunsell.com/blog/getting-started-raspberry-pi-install-raspbian
  • Configured some default options like password, system locale and so on after first start-up. Also enabled SSH (and disabled root login over ssh in /etc/ssh/sshd_config, (PermitRootLogin no)).
  • Followed a nice guide from http://www.codeproject.com/Articles/665518/Raspberry-Pi-as-low-cost-HD-surveillance-camera, with some modifications;
    • I’m not using the Raspberry Pi camera module, instead an old Logitech QuickCam Fusion, http://www.logitech.com/en-us/support/278?crid=405
    • updated the Raspberry Pi, sudo rpi-update
    • updated all packages, sudo apt-get update, sudo apt-get upgrade
    • Configured Wi-Fi following http://mattluurocks.com/index.php/raspbmc-dlink-dwa121-usb-pico-adapter
    • Checked that camera was detected (it was):

        root@xxx: /home/xxxx# lsusb
        Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
        Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
        Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
        Bus 001 Device 004: ID 046d:08c1 Logitech, Inc. QuickCam Fusion
        Bus 001 Device 005: ID 2001:3308 D-Link Corp. DWA-121 802.11n Wireless N 150 Pico

    • Installed the motion detection software:
      • sudo apt-get install motion
    • enabled motion deamon so it auto-starts in /etc/default/motion. Changed the line to: start_motion_daemon=yes
    • chmodded the files according to the above mentioned guide.
    • also edited /etc/motion/motion.conf following the guide, but managed to brake my own configuration 🙂 (motion process killed itself after a couple of seconds…)
      • A bit of detective work in /var/log/messages revealed:

          motion: [1] cap.card: “UVC Camera (046d:08c1)”
          motion: [1] cap.bus_info: “usb-bcm2708_usb-1.2”
          motion: [1] cap.capabilities=0x84000001
          motion: [1] – VIDEO_CAPTURE
          motion: [1] – STREAMING
          motion: [1] Config palette index 8 (YU12) doesn’t work.
          motion: [1] Supported palettes:
          motion: [1] 0: MJPG (MJPEG)
          motion: [1] 1: YUYV (YUV 4:2:2 (YUYV))
          motion: [1] Selected palette YUYV

      • changed the value to v4l2_palette 2 in motion.conf. Success! Motion now keeps running.
    • Made a directory for captures, mkdir /home/xxxx/captures , and pointed the configuration to that dir, “target_dir /home/xxxx/captures”
    • Had a look at http://www.lavrsen.dk/foswiki/bin/view/Motion/ConfigFileOptions
      • my own changes if someone is interested (along with the other changes above):
        • daemon on
        • width 640, height 480
        • framerate 5
        • pre_capture 2
        • post_capture 2
        • max_mpeg_time 600
        • output_normal off (I don’t need saved pictures, only videos)
        • ffmpeg_video_codec msmpeg4
        • webcam_port 8080
        • webcam_localhost off
        • control_port 8081
        • control_localhost off
        • control_authentication xxx:xxx

Setting up a cron job for motion:

I don’t want to have the cam running 24/7 so I decided to setup a cron job to fix that. Steps:

  • changed to root user instead of “xxxx” user, “sudo –s”
  • edited the crontab file, “crontab –e”
    • pasted the following:

      30 8 * * * /usr/bin/motion
      30 15 * * * /usr/bin/killall motion

    • Check the file/cron list with “crontab –l”

This will start motion at 8.30AM and shut it down at 3.30PM (daily)

Cron source: http://superuser.com/questions/169654/how-to-schedule-motion-detection


Securing (SSH on) the RPi

Because I forward the SSH port to the WAN side, I want to stay safe. (Yes, allowing to connect only with ssh keys is the safest method, I know, but a bit over the top for this project. Instead I’ll focus on securing ssh overall). Raspbian doesn’t seem to understand TCP wrappers (hosts.allow & hosts.deny), so I decided to use iptables instead. (Yes, I could have used another port than 22 also, but if some hacker want to get it in… they will anyhow). After a bit of fiddling I got it working.

At first, I installed a package called fail2ban (www.fail2ban.org), sudo apt-get install fail2ban. It automatically bans IP addresses that are failing to authenticate over SSH too many times. (The default fail2ban-options for SSH are OK for me, maxRetry = 6). This is the first layer of protection. After this I added some iptable rules for additional protection:

iptables -A INPUT -j ACCEPT -m state –state ESTABLISHED,RELATED (read comment in sources below, first link)
iptables -A INPUT -p tcp –dport 80 -m state –state NEW -j ACCEPT (open up port 80 for nginx web server)
iptables -A INPUT -p tcp –dport 8080 -m state –state NEW -j ACCEPT (open up port 8080 for motion’s own web server)
iptables -A INPUT -p icmp -m icmp –icmp-type 8 -j ACCEPT (open up for ping)
iptables -I INPUT -p tcp -m tcp -s xxxx.xxx.xxx.xx –dport 22 -j ACCEPT (SSH: my work pc)
iptables -I INPUT -p tcp -m tcp -s xxxx.xxx.xxx.xx –dport 22 -j ACCEPT (SSH: another linux login server)
iptables -I INPUT -p tcp -m iprange –src-range –dport 22 -j ACCEPT (SSH: access from internal network)
iptables -I INPUT -p tcp -m tcp -s –dport 22 -j DROP (SSH: deny all the rest)
iptables -P INPUT DROP (block all inbound traffic not accepted by a rule)


Then we should save the rules so they become persistent:
  • sudo bash -c ‘iptables-save > /etc/network/iptables’
  • then adding a line to /etc/network/interfaces so the changes will be persistent:
    • pre-up iptables-restore < /etc/network/iptables (add it after the line iface eth0 inet dhcp for ethernet connection or after iface wlan0 inet dhcp if on wlan)
  • Changes are now permanent

Source: http://www.simonthepiman.com/how_to_setup_your_pi_for_the_internet.php

We can check what the current iptables look like by looking at the (auto-created) file /etc/network/iptables:

root@xxxx:/home/xxxx# cat /etc/network/iptables
# Generated by iptables-save v1.4.14 on Tue Jun  3 15:53:59 2014
:INPUT DROP [27:4572]
:OUTPUT ACCEPT [90:10559]
:fail2ban-ssh – [0:0]
-A INPUT -p tcp -m multiport –dports 22 -j fail2ban-ssh
-A INPUT -s xxxx.xxx.xxx.xx/32 -p tcp -m tcp –dport 22 -j ACCEPT
-A INPUT -p tcp -m iprange –src-range -m tcp –dport 22 -j ACCEPT
-A INPUT -s xxxx.xxx.xxx.xx/32 -p tcp -m tcp –dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp –dport 22 -j DROP
-A INPUT -p tcp -m tcp –dport 80 -m state –state NEW -j ACCEPT
-A INPUT -p tcp -m tcp –dport 8080 -m state –state NEW -j ACCEPT
-A INPUT -p icmp -m icmp –icmp-type 8 -j ACCEPT
-A fail2ban-ssh -j RETURN
# Completed on Tue Jun  3 15:53:59 2014

and the same with iptables –L command:

root@xxx:/home/xxxx# iptables -L
Chain INPUT (policy DROP)
target     prot opt source                    destination
fail2ban-ssh  tcp  —  anywhere          anywhere             multiport dports ssh
fail2ban-ssh  tcp  —  anywhere          anywhere             multiport dports ssh
ACCEPT     tcp  —  xxxxx.xxx.fi             anywhere             tcp dpt:ssh (my workstation)
ACCEPT     tcp  —  anywhere               anywhere             source IP range tcp dpt:ssh
ACCEPT     tcp  —  xxxxx.xxx.fi             anywhere             tcp dpt:ssh (another linux login server)
DROP       tcp  —  anywhere                anywhere             tcp dpt:ssh
ACCEPT     all  —  anywhere                anywhere             state RELATED,ESTABLISHED
ACCEPT     tcp  —  anywhere               anywhere             tcp dpt:http state NEW
ACCEPT     tcp  —  anywhere               anywhere             tcp dpt:http-alt state NEW
ACCEPT     icmp —  anywhere             anywhere             icmp echo-request

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain fail2ban-ssh (2 references)
target     prot opt source               destination
RETURN     all  —  anywhere             anywhere
RETURN     all  —  anywhere             anywhere


Installing NGINX Web Server (for HTTP Authentication)

As the basic installation of motion doesn’t support authentication for the stream, I needed some other way of protecting it. My solution was to use NGINX Web server for authentication. I won’t use a reverse proxy to redirect directly to the stream, as I need a “middle page” with some html code (so I can watch the stream in any browser). More of that in the chapter “Motion MJPEG “fix” for any browser”.

To be able to watch the puppy cam from anywhere on the Internet and not only from your own LAN, you have to use port forwarding on your router. I won’t go into the details here as there are many different guides available on the net. That said, I forwarded port 80, 8080 and 22 from the internal network to the external network. (Yes, I’m using these default ports as a hacker will find the correct ports to hack anyway). I’ve also registered a  (free) dynamic-to-static dns address on www.noip.com. You can enter this noip-information into the routers configuration, but the configuration is different on different router brands. (It’s probably called something like “Dynamic DNS” though). With all this done I can now watch the puppy cam from any computer or device by just entering the web address http://(censored).noip.me (and login+password) in a browser. Anyways, here are the steps for installing and configuring nginx:

  • sudo apt-get install nginx
    • (Auto)start nginx service:  service nginx start
  • sudo apt-get install lynx (terminal based browser for testing). Linux curl-command can also be used.
  • Testing that it works: lynx – response: Welcome to nginx! (It works!)
  • Install apache utils to generate htpasswd files for authentication, sudo apt-get install apache2-utils
  • took a backup of /etc/nginx/sites-available/default –file. Then edited it:
    • changed root /usr/share/nginx/www; to root /home/xxx/www; (easier and more logical to edit and manage the webpage from /home).
    • created a htpasswd, sudo htpasswd –c /home/xxx/.htpasswd xxxxx
    • configured root dir on website to use htpasswd, under location / {
      • auth_basic “Restricted”;
      • auth_basic_user_file /home/xxx/.htpasswd;
  • The whole (tiny) configuration now looks like:

server {
        listen   80

        root /home/xxx/www;
        index index.html index.htm;

        location / {
                try_files $uri $uri/ /index.html;
                auth_basic “Restricted”;
             auth_basic_user_file /home/xxx/.htpasswd;


and my fancy index.html file looks like:

<title>Welcome to xxxxxxxcam!</title>
<body bgcolor=”white” text=”black”>
<center><h1>This is the xxxxx webcam stream!</h1></center><br>
<h3><a href=”http://censored.noip.me:8080″>Firefox link</a></h3><br>
<h3><a href=”index2.html”>IE/Chrome/Mobile link</a></h3><br>

and in a screenshot:


Fig 3. Main page (after I’ve entered login & password)

The Firefox-link links directly to the motion stream, as Firefox natively supports MJPEG. The IE/Chrome-link links to another webpage which uses java to display the mjpeg stream (see the chapter: Motion MJPEG “fix” for any browser). That page (Index2.html) looks like this:

<title>Welcome to xxxxxcam!</title>
<body bgcolor=”white” text=”black”>
<center><h1>This is the xxxxxx webcam stream!</h1></center><br>
<applet code=com.charliemouse.cambozola.Viewer
archive=cambozola.jar width=”640″ height=”480″ style=”border-width:1; border-color:gray; border-style:solid;”> <param name=url value=”http://censored.noip.me:8080″></applet&gt;

So basically what I’ve done is setup a password protected login page from which you can choose the method of displaying the stream.


Setting up a cron job for nginx:

Same principle as with motion, except:

35 8 * * * /etc/init.d/nginx start
35 15 * * * /etc/init.d/nginx stop

This will start nginx at 8.35AM and shut it down at 3.35PM (daily)

Useful nginx file locations:

/etc/nginx/sites-available and the default file
/etc/nginx and the nginx.conf file
/var/log/nginx and the error.log & access.log files

Starting and stopping the webserver:

service nginx start
service nginx stop




Motion MJPEG “fix” for any browser

The problem is that Internet Explorer (and other browsers as well) doesn’t support multipart jpeg (MJPEG). There’s a fix available at:


This assumes that you create a html page in which you include a bit of code. From the webpage:

The webserver generates a stream in “multipart jpeg” format (mjpeg). You cannot watch the stream with most browsers. Only certain versions of Netscape works. Mozilla and Firefox brosers can view the mjpeg stream but you often have to refresh the page once to get the streaming going. Internet Explorer cannot show the mjpeg stream. For public viewing this is not very useful. There exists a java applet called Cambozola which enabled any Java capable browser to show the stream. To enable the feature to a broad audience you should use this applet or similar.”


Securing NGINX with Fail2Ban

Well, I didn’t even have the server online for a long time before someone started probing/bombing for usernames and passwords (looking in the access and error logs). Sample from /var/log/nginx/error.log:

2014/06/09 15:38:12 [error] 4925#0: *24 user “manager” was not found in “/home/xxxx/.htpasswd”, client: 208.109.87.x, server: , request: “GET /manager/html HTTP/1.1”, host: “x.x.x.x”
2014/06/09 15:38:13 [error] 4925#0: *24 user “manager” was not found in “/home/xxxx/.htpasswd”, client: 208.109.87.x, server: , request: “GET /manager/html HTTP/1.1”, host: “x.x.x.x”
2014/06/09 15:38:14 [error] 4925#0: *24 user “user” was not found in “/home/xxxx/.htpasswd”, client: 208.109.87.x, server: , request: “GET /manager/html HTTP/1.1”, host: “x.x.x.x”
2014/06/09 15:38:16 [error] 4925#0: *24 user “user” was not found in “/home/xxxx/.htpasswd”, client: 208.109.87.x, server: , request: “GET /manager/html HTTP/1.1”, host: “x.x.x.x”

and from access.log:

208.109.87.x – manager [09/Jun/2014:15:38:12 +0300] “GET /manager/html HTTP/1.1” 401 194 “-” “-“
208.109.87.x – manager [09/Jun/2014:15:38:13 +0300] “GET /manager/html HTTP/1.1” 401 194 “-” “-“
208.109.87.x – user [09/Jun/2014:15:38:14 +0300] “GET /manager/html HTTP/1.1” 401 194 “-” “-“
208.109.87.x – user [09/Jun/2014:15:38:16 +0300] “GET /manager/html HTTP/1.1” 401 194 “-” “-“

Apparently “they” are trying to access /manager/html (Tomcat probing?) which doesn’t even exist on my site… oh well, this is not acceptable so I’ll have to block or ban these bastards. Of course I could try using https with certificates instead of http but it’s a bit overkill for this little server/hobby project 🙂

I started with a DDOS attack filter, info here: https://rtcamp.com/tutorials/nginx/fail2ban/

I then followed http://snippets.aktagon.com/snippets/554-how-to-secure-an-nginx-server-with-fail2ban to:

  • Block anyone trying to run scripts (.pl, .cgi, .exe, etc)
  • Block anyone trying to use the server as a proxy
  • Block anyone failing to authenticate using nginx basic authentication
  • Block anyone failing to authenticate using our application’s log in page
  • Block bad bots
  • Limit the number of connections per session

After this was done I ran:

root@xxx:/home/xxx# tail /var/log/fail2ban.log

2014-06-10 10:21:04,342 fail2ban.jail   : INFO   Jail ‘ssh’ started
2014-06-10 10:21:04,516 fail2ban.jail   : INFO   Jail ‘nginx-req-limit’ started
2014-06-10 10:21:04,618 fail2ban.jail   : INFO   Jail ‘nginx-auth’ started
2014-06-10 10:21:04,837 fail2ban.jail   : INFO   Jail ‘nginx-login’ started
2014-06-10 10:21:04,964 fail2ban.jail   : INFO   Jail ‘nginx-badbots’ started
2014-06-10 10:21:05,100 fail2ban.jail   : INFO   Jail ‘nginx-noscript’ started
2014-06-10 10:21:05,227 fail2ban.jail   : INFO   Jail ‘nginx-proxy’ started

(iptables –L now also lists a longer list with all these new fail2ban-rules. Won’t paste here as it’s a bit long…)

Luckily I did apply these filters, because the next day I got bombed by a ZmEu attack. Information about ZmEu:


Probably nothing to worry about as ISP’s are doing their own penetration testing all the time. Fail2Ban blocked it however (fail2ban.log):

2014-06-11 13:33:34,301 fail2ban.actions: WARNING [nginx-noscript] Ban 89.248.160.x
2014-06-11 13:43:34,409 fail2ban.actions: WARNING [nginx-noscript] Unban 89.248.160.x


With all this done, I now feel rather safe. After all, this is not a production server in Redmond 🙂

(If I do feel like experimenting with more security one day, I’ll compile my own Nginx with ModSecurity.  (http://www.modsecurity.org/projects/modsecurity/nginx/))

And there you have it – a rather nice and secure puppy cam. Enjoy! 🙂


Update: Version 2.0 of the Puppy Cam available here

5 thoughts on “(A rather secure) Raspberry Pi Puppy Cam

  1. Good afternoon!

    I also have recently made a puppy cam with my raspberry pi although I opted for the raspberry pi camera module instead of a usb webcam. Everything is working as it should (mine streams to ustream.tv) however I want to be able to schedule a crontab job like you did so that it will start and stop when I want. I’m having issues with this working, as detail here: http://www.raspberrypi.org/forums/viewtopic.php?f=28&t=89315 any insight on what I might be doing wrong? I appreciate your help (if any) in advance!!


    • Hi Tyler! Did you read my post? 🙂 Well, I got it working just like I have written, that is:

      30 8 * * * /usr/bin/motion
      30 15 * * * /usr/bin/killall motion

      I’m also modifying the current motion setup. I’m building Puppy Cam v.2.0, with RPi Camera Module and Pi-Pan. Also an Infrared Illuminator Board plate for better “night vision”. For that project I’ll be using OpenCV for the motion detection and mmal to record video for maximum framerate. Stay tuned for that :p

  2. Hi, I’ve used the about the same config you’ve used. Except I didn’t use fail2ban or iptables, or nginx (used apache) because I don’t need to be really secure, it’s just my dog in my kitchen.

    Anyway, I used Http Basic Auth, and I noticed that it works and you need a password, but you can just view it raw if I watch it off the stream_port (8081) without any password. Anyway to stop that?

    • Hi,

      I haven’t looked into this much after I made the OpenVPN server with another Raspberry Pi. All my cams are now secured behind the VPN, so I have no need for iptables or fail2ban anymore. (All the cams are just plain/unsecure nowadays, leaving security to the VPN server).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s