Post

Setting up Pi-hole on a Raspberry Pi

As ads and tracking can be considered a huge privacy and security concern I finally decided to setup a Pi-hole in my private network. So far, I was relying on browser add-ons (like uBlock Origin, NoScript) and Mullvad DNS, but that of course did not cover all my network devices.

Setting up a Pi-hole is actually pretty easy and I basically followed the official documentation and Mike Kuketz’ advices given here and here (German only). In any case, I’d always recommend going to the source first before following some guide on the internet.

Requirements

You can run a Pi-hole on any computer with a supported operating system, but I’d recommend getting a Raspberry Pi and installing Raspberry Pi OS (which is basically Debian 12 at the time of writing). Pi-hole is quite decent when it comes to hardware requirements. I went for a Raspberry Pi 4 model B with 8 GB RAM, but 1 or 2 GB RAM should be fine for a Pi-hole (mine is currently at ~270MB of RAM usage) - if you’re not planning to run anything else on it. Also, I’d recommend to get a good case. I got a full cover case with passive cooling capabilities so I won’t need a fan. Remember: the Pi will be running all the time and it’s probably less annoying without a noisy fan. For storage a 16GB SD card will do. I still got some 128GB SD cards left over from my old phones. In my case, the installation currently takes around 2.2 GB of storage. Also, don’t forget to add a power supply.

Once you have the hardware in place you can go ahead and install Raspberry Pi OS on the SD card. You can either do this with the official Raspberry Pi Imager or just make use of dd. I preferred the latter and just downloaded the 64-bit version of Raspberry Pi OS Lite from here. You will end up with a file called something like this 2024-03-15-raspios-bookworm-arm64-lite.img.xz. Compare the sha256 hash and if ok, unpack the file and write it to your SD card:

1
2
3
4
$ sha256sum 2024-03-15-raspios-bookworm-arm64-lite.img.xz
$ unxz 2024-03-15-raspios-bookworm-arm64-lite.img.xz
$ sudo dd bs=4M if=/<PATH_TO>/2024-03-15-raspios-bookworm-arm64-lite.img of=/dev/sd<X> conv=fsync status=progress
$ sudo sync

Make sure “/dev/sdX” is the correct device as you will loose all data on that device! Now we need to mount the SD card and create two files. The first one will simply enable sshd, allowing us to login via ssh. The second file will create the user “pi” with the initial password “raspberry”:

1
2
$ touch /<SD_CARD_MOUNT_PATH>/bootfs/ssh
$ echo "pi:$6$T6s1r19kJcX4WPpC$W5K/SPq6HE84yK.GwLuS2tqQhV6W4g6vHe/bIsBeE2rHJpbRXeQIEAjP0KnlCU5ifFfq.t96.KWX81iGO3kQV/" >> /<SD_CARD_MOUNT_PATH>/bootfs/userconf.txt

That’s it. Assuming you have already put all parts of the Pi together and also connected it to your router, you can now put the SD card into the card slot of the Pi and finally power it up.

Install Pi-hole

Adjust some settings and update

It will take one or two minutes until the Pi has booted up. It should receive an IP from your routers DHCP server. So, login to your router and check which IP was assigned to the Pi. Then connect via ssh as user “pi” using password “raspberry”:

1
$ ssh -l pi <IP>

I’d recommend setting a new password for user “pi” first, setting the time zone, optionally setting a new hostname and then do a full upgrade and reboot:

1
2
3
4
5
$ passwd
$ sudo raspi-config # go to Localisation Options -> Timezone
$ sudo hostnamectl <NEW_HOSTNAME>
$ sudo apt update && sudo apt dist-upgrade
$ sudo reboot

Energy saving (optional)

If you’re only running the Pi-hole software on your Pi, you can disable some hardware components which you will probably not need and thus save some energy. This was pointed out in Mike Kuketz’ instructions, which I followed. However, with Debian 12 the path of the configuration file changed a bit.

You will likely not require audio, bluetooth, WiFi or HDMI to work. To disable those hardware components edit /boot/firmware/config.txt:

  • search for dtparam=audio and change it to dtparam=audio=off
  • search for dtoverlay=vc4-kms-v3d and change it to dtoverlay=vc4-kms-v3d,noaudio
  • add those three lines before “[cm4]”:
    1
    2
    3
    4
    
    # Disable Bluetooth, WiFi and HDMI
    dtoverlay=disable-bt
    dtoverlay=disable-wifi
    hdmi_blanking=1
    

Then reboot again:

1
$ sudo reboot

Reduce I/O to SD card

To spare your SD card a little I/O you can optionally mv /tmp to RAM. For this to work you should probably have at least 2GB. The procedure is quite simple:

1
2
3
$ sudo cp /usr/share/systemd/tmp.mount /etc/systemd/system
$ sudo systemctl enable tmp.mount
$ sudo reboot

Set a static IP

Next, we need to assign a fixed IP address. You can do this either by configuring you router’s DHCP server to assign a fixed IP address to the Pi, or by setting a fixed IP on the Pi itself - make sure to choose one outside the router’s DHCP range. I’d prefer the latter. Assuming your network is 192.168.1.0/24 and your router’s IP is 192.168.1.1 we assign the IP 192.168.1.2 to the Pi in this example:

1
2
$ sudo nmcli con mod “Wired connection 1” ipv4.addresses 192.168.1.2/24 ipv4.gateway 192.168.1.1 ipv4.method manual
$ sudo reboot

Adjust IPs/networks according to your environment. If you’r locale is set to a different language than English, and thus the connection is not called “Wired connection 1”, check its NAME by:

1
$ nmcli con show | grep eth0

Install Pi-hole software

Connect to the Pi via the new IP. The Pi-hole project is proving a very convenient script to get everything up and running. I chose to clone the git repository and run the script from there. Once everything is done, you can delete your local copy of the repository again.

1
2
3
4
$ sudo apt install git
$ git clone --depth 1 https://github.com/pi-hole/pi-hole.git Pi-hole
$ cd "Pi-hole/automated install"
$ sudo bash ./basic-install.sh

Follow the on screen instructions:

  • Static IP Address: Yes: Set static IP using current values
  • Upstream DNS Provider: OpenDNS (ECS, DNSSEC)
  • Blocklists: Yes (include StevenBlack’s)
  • Admin Web Interface: Yes
  • Web Server: Yes
  • Enable Logging: Yes
  • Privacy mode FTL: Show everything (private Installation)

…and Pi-hole should be running in a couple of minutes. Take a note of the IP addresses and the password! To customize the password for the Pi-hole web interface, execute:

1
$ pihole -a -p

Install unbound

You can either configure Pi-hole to use your router as upstream DNS server (Settings -> DNS -> add your router’s IPv4 and if present IPv6), or install unbound on your Pi to do that job. Checkout the pros and cons here.

Install and configure unbound:

1
$ sudo apt install unbound

Now add a config file /etc/unbound/unbound.conf.d/pi-hole.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
server:
    # If no logfile is specified, syslog is used
    # logfile: "/var/log/unbound/unbound.log"
    verbosity: 0

    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes

    # May be set to yes if you have IPv6 connectivity
    do-ip6: no

    # You want to leave this to no unless you have *native* IPv6. With 6to4 and
    # Terredo tunnels your web browser should favor IPv4 for the same reasons
    prefer-ip6: no

    # Use this only when you downloaded the list of primary root servers!
    # If you use the default dns-root-data package, unbound will find it automatically
    #root-hints: "/var/lib/unbound/root.hints"

    # Trust glue only if it is within the server's authority
    harden-glue: yes

    # Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
    harden-dnssec-stripped: yes

    # Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
    # see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
    use-caps-for-id: no

    # Reduce EDNS reassembly buffer size.
    # IP fragmentation is unreliable on the Internet today, and can cause
    # transmission failures when large DNS messages are sent via UDP. Even
    # when fragmentation does work, it may not be secure; it is theoretically
    # possible to spoof parts of a fragmented DNS message, without easy
    # detection at the receiving end. Recently, there was an excellent study
    # >>> Defragmenting DNS - Determining the optimal maximum UDP response size for DNS <<<
    # by Axel Koolhaas, and Tjeerd Slokker (https://indico.dns-oarc.net/event/36/contributions/776/)
    # in collaboration with NLnet Labs explored DNS using real world data from the
    # the RIPE Atlas probes and the researchers suggested different values for
    # IPv4 and IPv6 and in different scenarios. They advise that servers should
    # be configured to limit DNS messages sent over UDP to a size that will not
    # trigger fragmentation on typical network links. DNS servers can switch
    # from UDP to TCP when a DNS response is too big to fit in this limited
    # buffer size. This value has also been suggested in DNS Flag Day 2020.
    edns-buffer-size: 1232

    # Perform prefetching of close to expired message cache entries
    # This only applies to domains that have been frequently queried
    prefetch: yes

    # One thread should be sufficient, can be increased on beefy machines. In reality for most users running on small networks or on a single machine, it should be unnecessary to seek performance enhancement by increasing num-threads above 1.
    num-threads: 1

    # Ensure kernel buffer is large enough to not lose messages in traffic spikes
    so-rcvbuf: 1m

    # Ensure privacy of local IP ranges
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10

…and restart unbound:

1
$ sudo systemctl restart unbound

Add another configuration file /etc/dnsmasq.d/99-edns.conf:

1
edns-packet-max=1232

Now go to Pi-holes web interface and add 127.0.0.1#5335 to Settings -> DNS as only Custom IPv4 address and leave all other Custom IPs blank (IPv4 and IPv6). Make sure to uncheck allUpstream DNS Servers” in the left box.

Hint: With Debian 12 I did not have to change anything with resolvconf.conf (as described in the original instructions, linked above).

Activate Pi-hole

Either configure your router’s DHCP server to set the Pi’s IPs (IPv4 and if present IPv6, printed out by the installer before) as DNS server, or disable your router’s DHCP server and make your Pi the DHCP server. Just make sure you only have ONE DHCP server running! In case you’d like to activate Pi-hole’s DHCP server, got to Settings -> DHCP.

Block lists

Of course, to work efficiently as blocker for unwanted content, we need some up-to-date blocking lists. Pi-hole already comes with a default list from https://github.com/StevenBlack/hosts. You might want to add some of his additional lists as well. I’d recommend the complete “Unified hosts + fakenews + gambling + porn + social”. This will also block all kinds of social media. In case you still want to be able to reach one of these sites you can simply add it to the white list. To add a list, go to Adlists and copy the URL of the host list into the box, optionally add a comment. Next you need to update the lists by going to Tools -> Update Gravity. The updater will automatically run once a week, triggered by cron (see /etc/cron.d/pihole).

Enable https for lighttpd

Even though we’re keeping everything inside our home network, I still dislike unencrypted connections. To enable https you need to create certificates first, or even better, your own CA. You can follow this post to get it done. Make sure to put key and certificate in one file, i.e.:

1
$ cat my_key.pem my_cert.crt > cert.pem 

Then, all you’d have to do is add a small configuration file to lighttpd’s config (adjust ssl.pemfile and ssl.ca-file). /etc/lighttpd/conf-available/98-ssl.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server.modules += ( "mod_openssl" )

# Enable the SSL engine with a cert, only for this specific host
$SERVER["socket"] == ":443" {
  ssl.engine = "enable"
  ssl.pemfile = "/etc/lighttpd/certs/cert.pem"
  ssl.ca-file =  "/etc/lighttpd/certs/ca-cert.pem"
}

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

Create a link to enable the new configuration file and restart lighttpd:

1
2
$ sudo ln -s /etc/lighttpd/conf-available/98-ssl.conf /etc/lighttpd/conf-enabled/98-ssl.conf
$ sudo systemctl restart lighttpd

You will now be automatically redirected to https.

Firewall

Though the Pi is running in a local network only, I’d still like to close all ports which are not required by running a firewall. My choice is firewalld (make sure your ssh port is still set to default port 22 before installing):

1
$ sudo apt install firewalld

Now let’s open the require ports. You can find instructions - also for other firewall frontends - here.

1
2
3
4
5
6
$ sudo firewall-cmd --permanent --add-service=http --add-service=https --add-service=dns --add-service=dhcp --add-service=dhcpv6
$ sudo firewall-cmd --permanent --new-zone=ftl
$ sudo firewall-cmd --permanent --zone=ftl --add-interface=lo
$ sudo firewall-cmd --permanent --zone=ftl --add-port=4711/tcp
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-all

If you’re not running the Pi-hole as your DHCP server, just omit --add-service=dhcp --add-service=dhcpv6.

That’s it :-)

This post is licensed under CC BY-SA 4.0 by the author.