Raspbian and Pi-hole together make an excellent ad-blocker, but there isn't much security out of the box to speak of. The following will harden the security up a little on you Pi-hole, which is never a bad thing.

User Management

Never leave a default username and password... just don't!

Create new user

Create a new user, so we can delete the default user pi

# Add a new user
$ sudo adduser username

Adding user `username' ...
Adding new group `username' (1001) ...
Adding new user `username' (1001) with group `username' ...
Creating home directory `/home/username' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for username
Enter the new value, or press ENTER for the default
	Full Name []: 
	Room Number []: 
	Work Phone []: 
	Home Phone []: 
	Other []: 
Is the information correct? [Y/n] y

# Check membership of sudoers group
$ sudo groupmems -l -g sudo

pi 

# Add new user to sudoers group
$ sudo usermod -a -G sudo username

# Check membership of sudoers group again to confirm the addition.
$ sudo groupmems -l -g sudo

pi  username 
Delete default pi user

Make sure you log in with your new user and test sudo before removing pi.

$ sudo deluser pi

Removing user `pi' ...
Warning: group `pi' has no more members.
Done.
Enable always require password for sudo

In order to have sudo prompt for a password (almost) all the time, we need to set a timout period for the password. How long you set this for is generally a personal preference, but I keep it short.

Add the following line to the Defaults section of the sudoers file using visudo

Defaults        timestamp_timeout=2

where the number is the number of minutes before the password times out. I highly recommend using visudo as it will do a syntax check of your file before updating it. If you save the file with incorrect syntax, noone can sudo. This is a very bad thing.

OS Security

Installing Updates

To check for updates and install manually, run:

# Check for new updates
$ sudo apt update 

# Install new updates
$ sudo apt upgrade

# Those commands can be compressed into a single command
$ sudo apt update && sudo apt upgrade

# To automatically approve theh updates, add -y
$ sudo apt update && sudo apt upgrade -y

To have updates installed on a schedule, we need to configure a cron job.

crontab -e

The first time you run this it will ask you to choose an editor. I use vi, but apparently nano is easier to work with. That will open the following file. The last two lines I've added.

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command
#  0 0 * * 0 root (apt-get update && apt-get -y upgrade) > /dev/null
 0 0 * * 0 root (apt-get update && apt-get -y upgrade) > /var/log/cronapt.log

The above entry will run an update midnight Sunday every week. You can output to /dev/null as per the commented out line if you don't want the log. I usually configure a log, at least initially to make sure everything is working properly.

Configuring the Firewall

Install the Uncomplicated Firewall. UFW provides an easy configuration tool for iptables. It isn't installed on Raspbian Lite by default.

$ sudo apt install ufw

By default, all incoming traffic is denied. We need to add rules for DNS, Web and SSH before enabling the firewall.

# Allow services inbound
$ sudo ufw allow in ssh
$ sudo ufw allow in dns
$ sudo ufw allow in http
$ sudo ufw allow in https

# This will deny connections if the same IP attempts more than 6 ssh connections in 30 seconds.
$ sudo ufw limit ssh/tcp

# Enable the firewall
$ sudo ufw enable

This may drop your ssh session. Always a good idea to have console access when configuring firewall rules.

SSL/TLS for the web interface

Install CertBot

$ sudo apt-get install certbot

The next steps requires DNS for the domain you are registering to resolve to the host running certbot, with port 80 available. This will probably require port forwarding to be configured on the router.

# Stop lighttpd
$ sudo systemctl stop lighttpd

# Request the certificate from LetsEncrypt 
$ sudo certbot certonly --standalone

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel):[email protected]

-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A
Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel):pihole.musingitoutloud.com
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for pihole.musingitoutloud.com
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/pihole.musingitoutloud.com/fullchain.pem.
   Your cert will expire on 2019-04-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Now that the certificates are created, we need to combine the certificate and key into a single pem.

# Combine the cert and key into a new file.
$ sudo cat /etc/letsencrypt/live/pihole.musingitoutloud.com/cert.pem > /etc/pihole/pihole.pem \
    && sudo cat /etc/letsencrypt/live/pihole.musingitoutloud.com/privkey.pem >> /etc/pihole/pihole.pem

# Copy the intermediate
sudo cp /etc/letsencrypt/live/pihole.musingitoutloud.com/chain.pem /etc/pihole/chain.pem

# Lockdown the permissions on the certificates
$ sudo chmod 600 /etc/pihole/*.pem

Configure Lighttpd

  • Create the file /etc/lighttpd/external.conf and add the following:
$HTTP["host"] == "pihole.musingitoutloud.com" {
  # Ensure the Pi-hole Block Page knows that this is not a blocked domain
  setenv.add-environment = ("fqdn" => "true")

  # Enable the SSL engine with a LE cert, only for this specific host
  $SERVER["socket"] == ":443" {
    ssl.engine = "enable"
    ssl.pemfile = "/etc/pihole/pihole.pem"
    ssl.ca-file =  "/etc/pihole/chain.pem"
    ssl.honor-cipher-order = "enable"
    ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
    ssl.use-compression = "disable"
    ssl.use-sslv2 = "disable"
    ssl.use-sslv3 = "disable"
  }

  # Redirect HTTP to HTTPS
  $HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
      url.redirect = (".*" => "https://%0$0")
    }
  }
}

Ensuring that the hostname and certificate paths match your own environment. Now you should be able to restart lighttpd.

$ sudo systemctl start lighttpd

If you want your Pi-hole available on the internet you will need to allow port 443 in the same way you allowed port 80. I only have my available internally and use a local host file for name resolution, temporarily enabling port forwarding as required certificate renewal.

Happy Days!


I used bit and pieces from the following in putting this together. Many thanks to the original contributors of this content.

Also, if you are really keen, Scott Helme has some excellent blog posts on automating LetsEncrypt renewals.