Building a PiHole for Privacy and Performance

11 min readJan 31, 2021


You can configure PiHole many ways, this guide focuses on privacy and performance.

Can you guess which is the PiHole?


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.)

Static IP Configuration

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 for NTP, with failback to the If you care to read about CloudFlare’s time service, there is a blog entry here:


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.


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: 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:


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 port 22

Enable the firewall:

sudo ufw enable

To show rules once the firewall is enabled, run the following command:

sudo ufw status verbose

Firewall Status

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 buster main" | sudo tee /etc/apt/sources.list.d/azlux.list
sudo wget -qO - | sudo apt-key add -
sudo apt-get update
sudo apt install log2ram


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

See the bottom line.

Now install RPi-Monitor:

sudo apt-get install dirmngr
sudo apt-key adv --recv-keys --keyserver hkp:// 2C0D3C0F
sudo wget -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

RPi-Monitor Status Page

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:

Core Temp 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 | bash

After some checks, you’ll be greeted with the install screen:

We’re going to change this later, so just hit Ok.
My network only supports IPv4, choose what works for you.
We set a static address, but hit Yes.
This is an important point, make sure there is a DHCP reservation for your PiHole.
You probably want the web admin page.
Install these modules.
Logs are half the fun, I recommend logging.
If this is a private network device, I recommend showing everything.

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: 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 -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.

# If no logfile is specified, syslog is used
# logfile: "/var/log/unbound/unbound.log"
verbosity: 0
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 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: fd00::/8
private-address: fe80::/10

Restart Unbound:

sudo service unbound restart

Test Unbound

dig @ -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

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 Click Save at the bottom.

Pointing PiHole to Unbound

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 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: and Anudeep’s whitelist project: If you work from home, please check out my Microsoft 365 whitelist:

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: 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.




Cybersecurity architect. Security dev and researcher. Infosec nerd. Linux enthusiast. All opinions and views are my own. Polite, professional, prepared.