Build

Step 1 /Raspi

  1. Technically, a Raspi is not required. The server hardware is your choice. See @DavidAns' Sky-Hole article for a virtual machine installation. If you haven't experienced the Raspi, my recommendation is to get one. It can even be the Zero as described in this Adafruit guide.

Step 2 /OS

  1. Now that you've obtained your Raspi, it needs the OS. Most often, this is Raspbian. If it didn't ship together with your Raspi, it's easy enough to download. The alternative is purely a personal choice. I wanted to have a bare Ubuntu Server that doesn't include the desktop, and window manager pre-installed.

Step 3 /Net

  1. Once you've completed the OS installation, your first step 1 should be an update. With Raspbian, your network link will work "out of the box". So proceed to
sudo apt update && sudo apt full-upgrade

If you went the Ubuntu Server route, the wifi requires manual setup. Determine the wireless interface first:

ip a

For me, this returned a list that included wlan0 which is the wireless interface. You'll need it to modify the /etc/netplan/wireless.yaml config file:

sudo cp /usr/share/doc/netplan/examples/wireless.yaml /etc/netplan/
sudo nano /etc/netplan/wireless.yaml

The example content should resemble:

network:
version: 2
renderer: networkd
wifis:
wlp2s0b1:
dhcp4: no
dhcp6: no
addresses: [192.168.0.21/24]
gateway4: 192.168.0.1
nameservers:
addresses: [192.168.0.1, 8.8.8.8]
access-points:
"network_ssid_name":
password: "**********"
The highlighted line 5 is where the wireless interface value goes. Note that there are other lines that need to change, too. We stay with the Pi-hole requirement of static 2 addressing. So lines 6 and 7 are fine as no . Line 8 must be changed to the IP that you reserve for the Raspi's wireless interface. In our diagram, we label it as 192.168.0.2 . Line 11 should be fine for now, but we'll come back after Unbound is running. Lastly, the access point fields are required, and specific to your wifi router. Exit and save the changes. Now we'll bring up the network:
sudo netplan --debug try

If there are no errors, we can continue:

sudo netplan --debug generate
sudo netplan --debug apply

With the network connected, we can proceed to the system update

sudo apt update && sudo apt full-upgrade

::: danger 1 As a rule, the first step should be to harden the system. See Ubuntu guide for a start, but at the minimum enable the firewall (i.e., sudo ufw enable). To allow other devices access to Unbound DNS, a port 53 rule will need to be created. :::

::: tip 2 To reserve an IP to use for a static address, you'll want to go into your wifi router's DHCP settings. Reserving the IP is also good to avoid accidental assignments by your DHCP service. :::

Step 4 /Unbound

  1. Here we depart a little from stock Pi-hole setup. Instead of Dnsmasq, we opt for Unbound. Unbound supports the blocking of domains just like Dnsmasq, while also giving us the ability to do recursive DNS. Use the steps from the recursive DNS guide which we’ll reproduce here:
sudo apt install -y unbound
curl -o root.hints https://www.internic.net/domain/named.root
sudo mv root.hints /var/lib/unbound/
sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf

With the file contents:

server:
verbosity: 0
port: 53
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
root-hints: "/var/lib/unbound/root.hints"
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
edns-buffer-size: 1472
prefetch: yes
num-threads: 1
so-rcvbuf: 1m
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

# Only answer devices in our net
access-control: 192.168.0.0/24 allow

# Block domains
include: /etc/unbound/unbound.bl

# Listen on the net interfaces
interface: 0.0.0.0
interface: ::0

Exit and save changes.

Step 5 /Block Domains

  1. Specify the domains to block. Fetch the list of domains 3 :
curl -o unbound.bl https://raw.githubusercontent.com/oznu/dns-zone-blacklist/master/unbound/unbound.blacklist
sudo mv unbound.bl /etc/unbound/

Now start Unbound DNS:

sudo service unbound start

::: tip 3 For convenience, this domain list comes ready to go by Oznu. Based on this project, I made a version in Golang adjusting for zone redirects according to Unbound man page. :::

Step 6 /Resolver

  1. Switch resolved from exporting queries now that Unbound is running.
sudo systemctl stop systemd-resolved
sudo nano /etc/systemd/resolved.conf

Which contains the DNSStubListener setting. Uncomment it and assign no. This will stop it from port 53 conflicts.

[Resolve]
#DNS=
#FallbackDNS=
#Domains=
#LLMR=no
#MulticastDNS=no
#DNSSEC=no
#DNSOverTLS=no
#Cache=yes
DNSStubListener=no
#ReadEtcHosts=yes

Exit and save changes.

Go back and revisit netplan wireless configuration. On this pass, we can change the nameservers:

sudo nano /etc/netplan/wireless.yaml

With content that resembles:

network:
version: 2
renderer: networkd
wifis:
wlan0:
dhcp4: no
dhcp6: no
addresses: [192.168.0.2/24]
gateway4: 192.168.0.1
nameservers:
addresses: [127.0.0.1]
access-points:
"network_ssid_name":
password: "**********"

Exit and save changes. Reboot.

sudo netplan --debug try
sudo netplan --debug generate
sudo netplan --debug apply
sudo reboot

Then link the active resolver configuration file:

sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf

Done. Your own personal DNS sinkhole appliance in a Raspi. Pointing your DHCP setting for DNS at your Raspi should stop ads from loading on web pages.