Blocking ads with Pi-hole
Introduction
If you are tired of intrusive ads cluttering web pages and interrupting your browsing experience, this guide is for you. It walks you through setting up Pi-hole on a Raspberry Pi, configuring it as your DNS server, and enjoying ad-free browsing even when you are away from home using Tailscale VPN. At the end of this post we will also go through the configuration needed for a highly available Pi-hole deployment.
What is Pi-hole?
Pi-hole is a DNS sinkhole which blocks DNS requests for known tracking and advertising domains, according to blacklists provided by users. It acts as a DNS server for a private network, comparing DNS queries against the domains listed in the blacklist. Pi-hole can process all DNS queries on your local network, so it blocks ads and tracking domains for every connected device. In contrast, traditional ad blockers run inside individual web browsers and only remove ads within that specific browser.
Pre-requisites
Deploying and configuring Pi-hole is straightforward, thanks to its comprehensive documentation. Before starting with the download, make sure the below list of main pre-requisites is met:
- a server with minimum 2GB free disk space and 512MB RAM.
- OS needs to be a Linux distribution (if the host OS of your machine is different, you need to leverage Docker).
- a static IP address for the server.
For the complete list of pre-requisites you can check this document.
Installation
I installed Pi-hole on a RaspberryPi 4B with 4GB RAM, more details on the hardware and connectivity are available in this article. The ISP provided router is a Nokia HA-140W-B, which (thankfully) allowed me to add a static DHCP Entry for my Raspberry Pi, creating a DHCP reservation that ties its MAC address to a static IP in my LAN subnet. Make sure you assign a static IP address to your server before continuing, the instructions to achieve this will differ based on your specific connectivity setup.
I believe it is worth going through the install script before running it, but if time is of the essence and you are aware of the risks of curling and piping to bash, run the below command:
curl -sSL https://install.pi-hole.net | bash
Click
Next you need to select the network interface according to your device. I have a wired Ethernet cable connectivity, therefore I selected the eth0 interface. If you are unsure, run the ifconfig command and pick the interface which has the static IP address you configured in the previous step assigned to it.
In the following step you will select the upstream DNS provider. A comparison of the main options is available in this article. Feel free to select the one which best meets your needs, I went with Cloudflare since it claims to be the fastest DNS service and has specific resolvers suited for families, filtering malware and adult content. Later we will go through an optional step where you can have your own recursive DNS resolver running on the same instance as Pi-hole, the benefits being enhanced privacy and no reliance on the ISP or publicly available DNS services since the local DNS server can query the root domain DNS servers directly.
The default blacklist, Steven Black’s hosts, is a good start and I recommend selecting it. After the Pi-hole is installed you can add extra blacklists, a good curated list to start with is available here. Feel free to enable query logging so that you have visibility into the domains that the Pi-hole is resolving.
After the successful installation, you should be able to access the Pi-hole UI at http://pihole-static-ip:80/admin using the password presented in the last tab of the installer menu. If you want to change this password you need to go to http://pihole-static-ip:80/admin/settings/all and look for webserver.api.password.
Make sure to go to http://pihole-static-ip:80/admin/gravity and update Gravity. Gravity is the core ad-blocking engine responsible for downloading, processing, and storing domain blocklists in a SQLite database. When a domain resolution request is made, Pi-hole checks if the domain is in the Gravity list; if it is and is not whitelisted, the request is blocked.
You are now ready to set your router’s primary DNS server to the IP address of your Pi-hole machine. You may also configure a public DNS resolver (for example, 1.1.1.1 from Cloudflare) as a secondary option so that internet access continues if Pi-hole becomes unavailable. Keep in mind, however, that if a secondary DNS server is configured, the router may still use it for some queries even when the primary DNS server is functioning normally. On most routers, changing DNS settings requires a reboot, which will briefly interrupt internet connectivity for a couple of minutes.
After the router reboots and you do a couple of DNS queries, you should be able to see several queries logged in the Pi-hole dashboard. Below you can see my DNS query activity in a 24 hours window:
Replacing the public DNS resolver
Instead of using the public DNS resolver, you can self-host a recursive DNS resolver such as unbound. The main benefit of using unbound is that it does not rely on third party services for resolution. It queries the root nameservers directly and also implements query name minimization, a process designed to reduce unnecessary data exposure by limiting the amount of information sent to upstream name servers during DNS resolution. I followed this Pi-hole guide to install unbound and to configure Pi-hole to use it as an upstream DNS server.
Ad blocking while away from home
You can forward DNS requests to your Pi-hole server from any private network. Thanks to Tailscale and their generous free plan, you can add multiple devices to your tailnet (a VPN operated by Tailscale) and configure the tailnet DNS to point to the Pi-hole server IP address.
Create a Tailscale account and follow the steps from this guide to add both your Pi-hole server and phone to the tailnet. After both devices appear as available in the Tailscale console, add the Tailscale IP of the Pi-hole to the DNS global nameservers in the DNS menu.
In order to forward DNS queries from your phone to Pi-hole, you need to make sure that all origins are allowed under Interface settings in http://pihole-static-ip:80/admin/settings/dns :
You should now be able to browse without ads on your phone whenever it’s connected to your tailnet.
Running highly available Pi-hole servers
If you have more than one server, you can set them up to run Pi-hole in a highly available configuration. To make this work, you need to do two things:
-
Synchronize the configuration from a primary Pi-hole instance to the secondary instances that act as backups.
-
Make all Pi-hole instances available under a single virtual IP address.
For step 1, if you are running Pi-hole version v6, you can use nebula-sync to synchronize the configuration from a primary Pi-hole to other Pi-hole replicas. I installed Pi-hole on a second Raspberry Pi and had a third one where I installed nebula-sync using a docker compose file. In the docker compose file below you will need to replace the IP addresses of the Pi-holes together with the passwords for each web interface:
---
services:
nebula-sync:
image: ghcr.io/lovelaze/nebula-sync:latest
container_name: nebula-sync
environment:
- PRIMARY=http://pihole1-ip|password
- REPLICAS=http://pihole2-ip|password
- FULL_SYNC=true
- RUN_GRAVITY=true
- CRON=*/30 * * * *
If the authentication information you provided is correct, you should see the below output in the nebula-sync container:
$ docker logs nebula-sync
INF Starting nebula-sync v0.11.1
INF Running sync mode=full replicas=1
INF Authenticating clients...
INF Syncing teleporters...
INF Syncing configs...
INF Invalidating sessions...
INF Sync completed
For step 2 you can leverage VRRP(Virtual Redundancy Routing Protocol). VRRP is a protocol that provides high availability and fault tolerance for routers in a network. It allows multiple routers to form a virtual router group and share a virtual IP address, which is used as the default gateway for the hosts in the network. Keepalived is a routing software which provides high availablity and load balancing in Linux systems, using VRRP.
You will need to install keepalived on both Pi-hole servers:
sudo apt install keepalived
On the primary Pi-hole, paste the contents of the file below in /etc/keepalived/keepalived.conf:
vrrp_instance pihole {
state MASTER
interface eth0
virtual_router_id 7
priority 255
advert_int 1
authentication {
auth_type PASS
auth_pass 12345
}
virtual_ipaddress {
192.168.1.250/30
}
}
Make sure to replace the virtual_ipaddress with an address that is outside your DHCP scope, preventing any other interface from using it.
Save the file and restart the keepalived service, you should see it running successfully.
On the second Pi-hole, paste the contents of the file below in /etc/keepalived/keepalived.conf:
vrrp_instance pihole {
state BACKUP
interface eth0
virtual_router_id 7
priority 254
advert_int 1
authentication {
auth_type PASS
auth_pass 12345
}
virtual_ipaddress {
192.168.1.250/30
}
}
Restart the keepalived service, in the logs you should see something like below:
pihole-secondary Keepalived[3654]: Starting VRRP child process, pid=3655
pihole-secondary Keepalived[3654]: Startup complete
pihole-secondary Keepalived_vrrp[3655]: (pihole) Entering BACKUP STATE (init)
pihole-secondary systemd[1]: Started keepalived.service - Keepalive Daemon (LVS and VRRP).
If you run the below from a device on your local network, you should get a response (make sure to replace the resolver IP address with the your VRRP IP address):
dig +ad dnssec.works @192.168.1.250
; <<>> DiG 9.10.6 <<>> +ad dnssec.works @192.168.1.250
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 245
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;dnssec.works. IN A
;; ANSWER SECTION:
dnssec.works. 3600 IN A 46.23.92.212
;; Query time: 98 msec
;; SERVER: 192.168.1.250#53(192.168.1.250)
;; WHEN: Sat Dec 27 16:55:55 GMT 2025
;; MSG SIZE rcvd: 57
Now you can simulate a failure of the primary Pi-hole by stopping the keepalived service. Below you can see the results from the keepalived logs from both instances.
Primary Pi-hole:
pihole-primary systemd[1]: Started keepalived.service - Keepalive Daemon (LVS and VRRP).
pihole-primary Keepalived_vrrp[1474]: (pihole) Entering MASTER STATE
pihole-primary Keepalived[1473]: Stopping
pihole-primary systemd[1]: Stopping keepalived.service - Keepalive Daemon (LVS and VRRP)...
pihole-primary Keepalived_vrrp[1474]: Stopped
pihole-primary Keepalived[1473]: Stopped Keepalived v2.3.3 (03/30,2025)
pihole-primary systemd[1]: keepalived.service: Deactivated successfully.
Secondary Pi-hole:
pihole-secondary Keepalived_vrrp[3655]: (pihole) Entering MASTER STATE
A new DNS query will shift to the secondary Pi-hole via the VRRP IP, ensuring high availability.
Conclusion
In this post, we walked through setting up Pi-hole for network-wide ad blocking, integrating it with Tailscale to extend the same protection to mobile devices when you are away from home and building a highly available Pi-hole deployment using VRRP. With this setup, you get private, consistent, and resilient DNS-based ad blocking across your devices, whether you are on your home network or connected remotely, while minimizing downtime through automatic failover.
comments powered by Disqus