You can configure PiHole many ways, this guide focuses on privacy and performance.
Requirements
PiHole is a popular DNS level ad block that can also protect against tracking and telemetry. It’s fairly light weight, so any Raspberry Pi with an Ethernet port will support it. I would not recommend a Pi Zero. A Raspberry Pi 3B+ is more than sufficient to run PiHole. You’ll also need a Micro SD Card; I’d recommend 16 GB, but 8 GB is enough to install PiHole. I’d recommend a case and power supply as well. You’ll also need an Ethernet cable and a computer to configure the server. Amazon has kits available for the 3B+ ranging from $60 to $80, with a 3B+ available for $45, but I’m sure you can find individual components cheaper elsewhere.
Getting Started
Flash Rasbian Lite onto a blank Micro SD Card. There are many ways to do this, so choose your favorite (Etcher, Raspberry Pi Imager, dd, etc.) Once your SD Card has been imaged, create a ssh file on the boot partition via touch ssh
or PowerShell $Null | Out-File .\ssh
or New > Text Document, name it ssh and remove the .txt.
Insert the Micro SD Card into your Pi and power it up. Find the IP on your network and SSH into it. Run raspi-config
to set localization, time zone, GPU memory split (I usually cut it down to 8MB), and expand the file system. Also set the hostname. I don’t recommend setting up WiFi.
After reboot update your system.
sudo apt-get update && sudo apt-get upgrade -y
Install dnsutils
sudo apt install dnsutils
Install git
sudo apt install git
Configure the system for a static IP
sudo nano /etc/dhcpcd.conf
Uncomment the values for “Example static IP configuration” and provide your own. I’m using CloudFlare for the systems DNS, but this is only for lookups that this system performs (packages, git, etc.)
Save and reboot. Login and verify static IP and DNS.
Configure NTP. Check the current configuration:
timedatectl status
Check timedatectl configuration:
sudo nano /etc/systemd/timesyncd.conf
Comment out the last line and configure the time servers. I’m using time.cloudflare.com for NTP, with failback to the debian.pool.ntp.org. If you care to read about CloudFlare’s time service, there is a blog entry here: https://blog.cloudflare.com/secure-time/
Exit and save the file.
Ensure time synchronize is enabled:
sudo timedatectl set-ntp true
Install the unattended upgrades package:
sudo apt install unattended-upgrades
Review the configuration file:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
You may want to update some settings, I recommend uncommenting and changing “Unattended-Upgrade::Remove-Unused-Dependencies” to “true”. Exit and save the file.
Create a periodic upgrade file:
sudo nano /etc/apt/apt.conf.d/02periodic
This should be empty, paste the following into the contents:
APT::Periodic::Enable “1”;
APT::Periodic::Update-Package-Lists “1”;
APT::Periodic::Download-Upgradeable-Packages “1”;
APT::Periodic::Unattended-Upgrade “1”;
APT::Periodic::AutocleanInterval “1”;
APT::Periodic::Verbose “2”;
Exit and save.
Check your unattended upgrades by running this command to debug your configuration:
sudo unattended-upgrades -d
Change the default password for Pi and put it in your password manager.
passwd
Create a new user.
sudo adduser <username>
Give the new user sudo.
sudo adduser <username> sudo
You may need to add them to the video group for some monitoring applications as well, so add them to that group too.
sudo adduser <username> video
Log out and log back in as the new user. Test and verify sudo. Lock the Pi account:
sudo passwd –l pi
Lock down the SSH service. Edit the SSH config file.
sudo nano /etc/ssh/sshd_config
At the very least, uncomment this line:
#PermitRootLogin prohibit-password
I also recommend uncommenting #MaxAuthTries 6
If you know what IP you’ll be connecting from 100% of the time, you can configure that as well. I’d also recommend setting up SSH keys, here is an article on how to do that if you’re unfamiliar: https://kb.iu.edu/d/aews If you have SSH keys setup you can configure this line in the config: PasswordAuthentication no
Save and exit. Restart SSH:
sudo service ssh restart
Install Fail2Ban.
sudo apt install fail2ban
Fail2ban will block attackers IP if they fail to login after 5 failures for 10 minutes. Note: Fail2Ban installed from the repo will only provide security on IPv4. If you want Fail2Ban to support IPv6, please look at this guide.
You can configure fail2ban here:
/etc/fail2ban/jail.conf
If you make any config changes, restart the service via:
sudo service fail2ban restart
Install a firewall. Caution, don’t lock yourself out of your server.
Install the package:
sudo apt install ufw
Create your access list:
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 53
sudo ufw allow 8888
sudo ufw allow 22
You can be more restrictive with rules, like SSH for example. You can only allow access on port 22 from your computer’s IP address:
sudo ufw allow from 192.168.1.120 port 22
Enable the firewall:
sudo ufw enable
To show rules once the firewall is enabled, run the following command:
sudo ufw status verbose
Log2ram is created for the Raspberry Pi. Since the Raspberry Pi uses a micro SD card for storage, constantly writing logs creates a lot of IOPS which can degrade the SD card. Log2ram creates a virtual /var/log/ directory in memory and synchronizes them back to the physical disk periodically. This reduces IOPS on the micro SD Card (if you’re logging DNS queries.) One complication is that logs stored in memory that do not get written to disk (because of a reboot for example) can make debugging an issue harder to track down. This is suggested for a PiHole because of how much logging the server is going to do, but be aware of the possible issues.
Install log2ram.
sudo echo "deb http://packages.azlux.fr/debian/ buster main" | sudo tee /etc/apt/sources.list.d/azlux.list
sudo wget -qO - https://azlux.fr/repo.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt install log2ram
Reboot.
sudo reboot
Configure log2ram:
sudo nano /etc/log2ram.conf
Increase the size to 100MB and the LOG_DISK_SIZE to 200M. Exit and save. Restart log2ram.
sudo service log2ram restart
Check that log2ram is running.
df -h
Now install RPi-Monitor: https://github.com/XavierBerger/RPi-Monitor
sudo apt-get install dirmngr
sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 2C0D3C0F
sudo wget http://goo.gl/vewCLL -O /etc/apt/sources.list.d/rpimonitor.list
sudo apt-get update
sudo apt-get install rpimonitor
Configure RPi-Monitor to show network statistics:
sudo nano /etc/rpimonitor/template/network.conf
Uncomment the first two sections that start with “dynamic.10” and “dynamic.11”. Comment out the third, fourth and fifth lines in the next section that start with “web.status.1” and uncomment the last one. Uncomment the next section that starts with “web.statistics.1”. Exit and save.
Restart RPi-Monitor.
sudo service rpimonitor restart
Update RPi-Monitor package status:
sudo /etc/init.d/rpimonitor update
Check the RPi-Monitor web page at http://<IPAddress>:8888
You now have a web dashboard of your server’s status, and there is a historical view under Statistics. This can be helpful for monitoring and troubleshooting. Here is a view in Statistics of temperature over 14 days:
Install and Configure PiHole
Now that Raspbian is configured and secured, we can install PiHole. The install is very simple:
sudo curl -sSL https://install.pi-hole.net | bash
After some checks, you’ll be greeted with the install screen:
And off it goes.
When the installation is complete you will get a final screen with some important info. Take note of this:
Record the admin webpage password in your password manager for now, it should be changed later.
This same info is displayed once you return to the shell, note the command to change the web admin password (pihole -a -p
):
Enhancing PiHole Security
So now we have a working PiHole, but it has minimal blocking and just forwards lookups to Google DNS. We can change our upstream DNS provider, but that is just changing who we trust with our DNS. What if we don’t trust anyone? We can install Unbound and resolve DNS ourselves using root servers to recursively resolve DNS names. A more in depth explanation of how this works can be found here: https://docs.pi-hole.net/guides/dns/unbound/ but essentially Unbound will look up a DNS query by asking TLD servers for DNS in a recursive manner. The benefit is more security; you do not have to trust an upstream provider with your DNS traffic. The drawback is performance for initial lookups, as they need to traverse and this takes time. PiHole and Unbound can both be configured with caching, which will help mitigate this for subsequent lookups.
Install Unbound.
sudo apt install unbound
Get root hints:
wget https://www.internic.net/domain/named.root -qO- | sudo tee /var/lib/unbound/root.hints
Create the configuration for PiHole:
sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf
Paste into the file this configuration. This is different than the one in PiHole’s documentation. It includes caching configuration that will improve performance.
server:
# If no logfile is specified, syslog is used
# logfile: "/var/log/unbound/unbound.log"
verbosity: 0interface: 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.
# Suggested by the unbound man page to reduce fragmentation reassembly problems
edns-buffer-size: 1472# Perform prefetching of close to expired message cache entries
# This only applies to domains that have been frequently queried
prefetch: yes
cache-min-ttl: 0
serve-expired: yes
msg-cache-size: 128m
rrset-cache-size: 256m# 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
Restart Unbound:
sudo service unbound restart
Test Unbound
dig pi-hole.net @127.0.0.1 -p 5335
Let’s setup some cron jobs to keep the server updated, including PiHole and Unbound.
sudo crontab –e
Paste the following:
30 2 * * SUN pihole -up
05 01 15 */3 * wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
Exit and save.
Note: The PiHole team does not recommend updating PiHole via cron jobs ( pihole -up
). Be aware that your server will update PiHole every Sunday via cron, and stay up-to-date on patch notes. If there is a major change, and you don’t want to update, sudo crontabe -e
and comment out the line to update PiHole (place a # before the line.)
Login to your PiHole admin page at http://pi.hole/admin and use the password you saved from the install. Navigate to Settings, and click on the DNS tab. Uncheck Google and check custom and enter 127.0.0.1#5335
. Click Save at the bottom.
As things get queried initial performance will be slow but quickly improve because of the caching nature of PiHole and the cache that has been configured for Unbound. Here is an example:
67ms is not great, but average response from CloudFlare DNS is 20ms, and there is no caching on the second request. Once your PiHole has been online for 12 hours, DNS response will be excellent.
Blocking and Filtering
Now that you have a fast and private DNS setup on with your PiHole, it’s time to look at block lists, whitelists, and blacklists. Block lists are lists maintained of bad domains that could be ads, malware, or tracking. I have 1.5 million domains from my various block lists, and some overlap. A good resource for block lists is https://firebog.net/ which has several categories of block lists. An issue with block lists is that unintended domains will get blocked, preventing you from accessing legitimate content. This is where whitelists come into play. A good resource for whitelists is the commonly whitelisted domain page: https://discourse.pi-hole.net/t/commonly-whitelisted-domains/212 and Anudeep’s whitelist project: https://github.com/anudeepND/whitelist If you work from home, please check out my Microsoft 365 whitelist: https://github.com/TheSmashy/O365Whitlist
Blacklist are for targeted or specific issues, but you can also add regex entries to blacklist to provide more comprehensive blocking. A good place to find regex would be motti’s regex github: https://github.com/mmotti/pihole-regex this would be a good baseline for blacklisting. The instructions provide a simple way to install the regex directly into your PiHole.
What is the Payoff?
Once everything is configured, you have a secure, private, and fast DNS solution that increases the DNS health of your network and protects users, as well as keeps your DNS information more private. You don’t have to trust anyone with your DNS traffic, and the performance and security on your network is better than any service you can purchase. There is more administrative overhead, but privacy and security are always an investment of some sort.