I have a friend who wanted to serve some simple PHP scripts over HTTPS from a Raspberry Pi. He’d seen some benchmarks showing that Apache was a bit of a hog so was trying to use Nginx but was having trouble because none of these apps are in the Raspbian repos.

I’m a total noob when it comes to Linux (I got my first Pi a week or so ago), but it didn’t seem like this should be a complicated setup, so I decided to see if I could quickly get it working. I have plenty of SD cards lying around and my Pi wasn’t doing anything important so there’s nothing to lose.

Goals

The main goal for me was to take a new install of Raspbian (I’m using Raspbian Jessie Lite, but standard Raspbian via NOOBS should be fine too) and render a PHP “Hello World” over HTTPS with a real cert trusted by browsers with minimal effort, while:

  • Preferring NGINX or Lighttpd to Apache
  • Preferring PHP 7 to anything older
  • Getting free TLS certs from LetsEncrypt
  • Scheduling renewal of TLS certs via cron

Replacements

If you’re going to use any of these scripts, there are two things littered throughout that you must replace with your own values:

  • /var/www/html is the webroot. I’m using the standard location Lighttpd (and Apache!) use out-of-the-box.
  • pi.dantup.com is the public hostname for the site/cert. This is mine; replace it with your own!

Complications

  • The LetsEncrypt client is not available in the Raspbian Jessie repositories (nor debian Jessie)
  • PHP 7 is not available in the Raspbian Jessie repositories (nor Debian Jessie or Jessie Backports)

Getting Raspbian

Step 1 is to get a working Raspbian install. I chose to use Raspbian Jessie Lite from here because I tend to run my Pis headless. These instructions should all be the same for standard Raspbian (inc. via NOOBS).

Security Considerations

If you’re going to expose your Pi (or any other computer) to the web, you should consider the security. Someone breaching a device on your network could give them access to other devices inside your network! Some things you might wish to consider doing to your Pi if you’re going to expose it (especially if opening SSH):

  • Create your own user and ditch the Pi user (at a minimum, change the password to something strong!)
  • Disable root login over SSH
  • Change the SSH port
  • Set up unattended security upgrades

I’ve been working on a script that does this (and more) for me when I set up a new Pi. I’m hoping to finish/tidy/blog it over the coming weeks.

Setting up Raspbian

First thing I do when setting up Raspbian is expand the filesystem (you won’t need to do this if using NOOBS, but will if you’ve written a Raspbian .img) and ensure all packages are up-to-date.

# Expand the filesystem and reboot
sudo raspi-config --expand-rootfs
sudo reboot

# Update lists, perform upgrades, remove orphaned packages
sudo apt-get update -y && sudo apt-get dist-upgrade -y && sudo apt-get autoremove -y

Switch to root shell

Pretty much everything here requires root, so it’s easiest to switch to a root shell to avoid prefixing everything with sudo.

sudo -i

Installing Lighttpd

This one is pretty painless. Install via apt and replace the default page with our own Hello World.

apt install lighttpd -y
rm /var/www/html/index.lighttpd.html
echo '<h1>Hello, World!</h1>' > /var/www/html/index.html

At this point you should be able to visit http://raspberrypi/ (assuming you haven’t changed the hostname) and see your Hello World message. It’s a good start, but it’s not HTTPS!

Installing packages from Debian

Because neither PHP 7 nor the LetsEncrypt client are in the Raspbian repos, we need to fetch them from the Debian ones. Before apt will download things from there, we need to trust them. You should probably confirm these keys (such as by skipping this step, letting the next step fail, and then getting the keys from the error message) rather than copy/paste them from some random guys blog (which wasn’t served over HTTPS) though!

The error you’ll get prior to trusting these keys looks like this:

W: GPG error: http://http.debian.net jessie-backports InRelease: The following
signatures couldn't be verified because the public key is not available:
    NO_PUBKEY 8B48AD6246925553 NO_PUBKEY 7638D0442B90D010

I don’t know why there are two. Did I mention I’m a Linux noob?

gpg --keyserver pgpkeys.mit.edu --recv-key  8B48AD6246925553      
gpg -a --export 8B48AD6246925553 | apt-key add -
gpg --keyserver pgpkeys.mit.edu --recv-key 7638D0442B90D010      
gpg -a --export 7638D0442B90D010 | apt-key add -

Install the LetsEncrypt Client

The LetsEncrypt client is available from Jessie Backports. We’ll need to add the source to be able to install it from there. My script removes the source at the end (and re-updates the packages; I don’t know if that’s required) since we only want to borrow these packages and not use this for anything more.

echo "deb http://httpredir.debian.org/debian jessie-backports main contrib non-free" > /etc/apt/sources.list.d/debian-jessie-backports.list
apt-get update -y
apt install letsencrypt -t jessie-backports -y
rm /etc/apt/sources.list.d/debian-jessie-backports.list
apt-get update -y

Requesting TLS Certs

In order for the LetsEncrypt client to work, it needs to be able to connect to your Pi using the hostname you want the cert for (this is to verify you actually own this domain, or at least, control the web server it points at). This may mean setting up DNS records to point the domain at your Pi and/or forwarding ports from a router (you’ll need port 80 for this step, but may as well also map 443 while you’re at it to save coming back to it later).

Because there’s no automatic module to set up Lighttpd we need to use the --webroot method (this means the client will write files into your webroot and LetsEncrypt will connect back and read them). Pass each webroot with -w and the domain with -d. You can add multiple sets of these as required.

Remember to replace the webroot and domain with your own!

letsencrypt certonly --webroot -w /var/www/html -d pi.dantup.com

Setting up TLS in Lighttpd

Lighttpd expects certs to be combined, so we need to concatonate them before we can configure it. Remember to replace your domain in the path (note: this is in /etc/letsencrypt and not your webroot!).

pushd /etc/letsencrypt/live/pi.dantup.com/
cat privkey.pem cert.pem > combined.pem
popd

Next we need to add TLS config for Lighttpd. We need to point at the cert we just combined as well as the full chain certificate that will be served up to the client browser. We also disable SSLv2 and SSLv3 for security.

Again, be sure to replace your domain in the certificate paths.

tee /etc/lighttpd/conf-enabled/letsencrypt.conf > /dev/null <<EOF
$SERVER["socket"] == ":443" {
        ssl.engine = "enable"
        ssl.pemfile = "/etc/letsencrypt/live/pi.dantup.com/combined.pem"
        ssl.ca-file =  "/etc/letsencrypt/live/pi.dantup.com/fullchain.pem"
        ssl.cipher-list = "ECDHE-RSA-AES256-SHA384:AES256-SHA256:HIGH:!MD5:!aNULL:!EDH:!AESGCM"
        ssl.honor-cipher-order = "enable"
        ssl.use-sslv2 = "disable"
        ssl.use-sslv3 = "disable"
}
EOF
/etc/init.d/lighttpd force-reload

At this point, you should now be able to visit https://raspberrypi/ (assuming you haven’t changed the hostname) and see your Hello World message again. You’ll get a warning about the cert name not matching (since you’re not accessing it via the real domain), but that’s expected. Accessing it via the real domain may work depending on your setup. Hurrah! Encryption!

Installing PHP 7

Like the LetsEncrypt client, PHP 7 is not available in the Raspbian repos. Nor is it available in Debian Jessie Backports. However it is available in Stretch (the next version after Jessie),so we can do a similar thing and get it from there.

echo "deb http://httpredir.debian.org/debian stretch main contrib non-free" | tee /etc/apt/sources.list.d/debian-stretch.list
apt-get update -y
apt install php7.0 php7.0-fpm -t stretch -y
rm /etc/apt/sources.list.d/debian-stretch.list
apt-get update -y

Next we need to enable fastcgi and tell Lighttpd where to find PHP.

tee /etc/lighttpd/conf-enabled/php.conf > /dev/null <<EOF
fastcgi.server += (".php" => ((
        "socket" => "/var/run/php/php7.0-fpm.sock"
)))
EOF
lighttpd-enable-mod fastcgi
/etc/init.d/lighttpd force-reload

Finally, let’s replace our boring old Hello World with a nice PHP one!

rm /var/www/html/index.html
echo '<?php echo "<h1>Hello, World (from PHP)!</h1>"; ?> ' > /var/www/html/index.php

If you’ve done all this right, https://raspberrypi/ (assuming you haven’t changed the hostname) will now greet you from PHP! Almost done…

Automatic TLS Cert Renewal

Not renewing your certificates would be pretty embarassing, so we should make that automatic. We’ll do this via Cron. Cron sends emails with the output of commands, so if you’ve set up emails to be forwarded to a real mailbox (this will be covered in my Pi setup script which I hope to blog in the coming weeks) you’ll get a note each time this runs. We’ll set it to run weekly since by default it only renews certs that’ll expire in the next 30 days, so monthly could cause them to be missed.

Edit: Mike Scalora posted a better version of this that handles multiple domains neatly in the comments :)

tee /etc/cron.weekly/letsencrypt > /dev/null <<EOF
# Renew cert
letsencrypt renew

# Rebuild the cert
pushd /etc/letsencrypt/live/pi.dantup.com/
cat privkey.pem cert.pem > combined.pem
popd

# Reload
/etc/init.d/lighttpd force-reload
EOF
chmod +x /etc/cron.weekly/letsencrypt

That’s It!

I believe that’s everything! If you hit problems please post in the comments and I’ll try to fix them up. Please note that I’m a Linux noob, some (or all) of this might not be done in the best possible way. Again, I’ll make tweaks if people send corrections/improvements :-)