How to block ADS with PiHole!

Hello there! Are you ready to block ADS once and for all? Good ᗜ⩊ᗜ

BUT.

First, we need to address a couple of things.

In order for you to fully comprehend PiHole’s capabilities and limitations, we first need to do a small deep dive.

It won’t take long.

Pinky promise >⩊<

PiHole is a network level DNS “sinkhole” designed to basically block ADS , trackers and potentially malicious domains across all devices on a network without requiring client side software; pretty neat!

It operates by intercepting DNS queries and selectively refusing or redirecting those that match known blocklists (you can find popular blocklists on Github)

1. DNS based blocking

PiHole leverages the domain name system.

What does that mean?

when a device requests a domain (e.g ads.example.com), a DNS resolver translates that into an IP address.

PiHole sits between them into this process:

  1. A client sends a DNS query.
  2. PiHole receives the query instead of a public resolver.
  3. It checks the domain against blocklists.
  4. If blocked → returns a “null” response (e.g. 0.0.0.0 or NXDOMAIN)
  5. If allowed → forwards the query to an upstream DNS server.

Your PiHole installation consists of several tightly integrated components:

1. A DNS engine

  • Powered by FTLDNS, a custom fork combining:
  • dnsmasq (lightweight DNS server)
  • additional telemetry and performance logic (wont go into details hehe sorry qwq)

  • It handles:
    • query parsing
    • cache management
    • blocklist matching
    • statistics collection

2. Web Interface

  • Built with:
    • PHP
    • JS
  • Displays:
    • query logs
    • top domains
    • block rates
    • client activity

3. Gravity database

  • it’s a compiled SQLite database:
    • source lists → merged → deduplicated → optimized
  • stored as gravity.db
  • Contains:
    • blocked domains
    • allowlists
    • regex filters

When a device sends a DNS request:

@ ads.tracker.com
  • PiHole listens on port 53 (TCP).
  • It receives the query via:
    • router DHCP configuration
    • manual DNS settings
    • transparent DNS redirection

4. PiHole then evaluates:

a. exact match with hash lookup

  • O(1) lookup via inmemory hash tables

b. regex matching

  • using compiled regex rules (slower, but feasible ig qwq)

c. whitelist check

  • overriding block decisions

Decision logic

conditionaction
Domain in blocklistReturn sinkhole IP
Domain allowedForward upstream
CachedReturn cached response

5. Upstream resolution

If allowed and set up:

  • Forwarded to providers like:
    • Cloudflare (1.1.1.1)
    • Google (8.8.8.8)
  • Response cached for TTL duration

6. Response to client

  • Returned to requester
  • Logged in PiHole database

What’s really interesting about PiHole is its “sinkhole” technique

Instead of resolving:

ads.example.com → 192.168.1.100
Code language: CSS (css)

PiHole returns:

ads.example.com → 0.0.0.0
Code language: CSS (css)

So the connection fails instantly and no HTTP request is ever made : P

PiHole can respond:

NXDOMAIN (non-existent domain)

PiHole operates at DNS level, meaning it blocks the request before connection establishment.

Unlike firewall rules, it prevents even initial TCP handshake! Gravity list is loaded into fast RAM and avoids disk lookups during queries!

Limitations & weaknesses

Hardcoded DNS

Although quite powerful, PiHole cannot perform magic against apps that bypass it using build in DNS.

CDN & shared domains

Blocking a domain may break legit services hosted on the same domain as well ¯\_(ツ)_/¯

That’s it. Told ya it would be quick! Hehe, seriously, if you have any questions just drop a comment down below and i’ll try to answer exhaustively :3

Before you begin

  1. Update your system:
sudo apt update && sudo apt upgrade -y

Method 1: automated script installation

Quick Installation (Recommended)

The easiest way to install PiHole is using the official automated installer.

1. download and run the installer:

curl -sSL https://install.pi-hole.net | bashCode language: JavaScript (javascript)

Or if you prefer to review the script first:

wget -O basic-install.sh https://install.pi-hole.net
sudo bash basic-install.shCode language: JavaScript (javascript)

2. follow the prompts

The installer will guide you through:

  1. Network interface selection: Choose the network interface PiHole will use (usually eth0 for Ethernet or wlan0 for WiFi)
  2. Upstream DNS provider: Select your preferred DNS provider:
    • Google (8.8.8.8, 8.8.4.4)
    • OpenDNS (208.67.222.222, 208.67.220.220)
    • Cloudflare (1.1.1.1, 1.0.0.1)
    • Quad9 (9.9.9.9, 149.112.112.112)
    • Custom
  3. Blocklists: Confirm to use the default blocklists (recommended)
  4. Admin web interface: Choose to install the web admin interface (recommended)
  5. Web Server: Choose to install lighttpd web server (recommended)
  6. Query Logging: Enable query logging (recommended for statistics)
  7. Privacy Mode: Select your preferred privacy level

Note your login info

At the end of installation, you’ll see:

Admin password (save this!)

Web interface URL (usually http://pi-hole/admin or http://your-ip/admin)

To change the admin password later:

pihole -a -p

Method 2: manual installation

Okay this one’s a bit more complicated. If you are like me though, you don’t wanna skip it. I always do everything manually and compile my own stuff (Gentoo user hehe (• ω•))

1. Install dependencies

On Debian/Ubuntu/Raspberry Pi OS:

sudo apt update
sudo apt install -y git curl wget dnsutils net-tools
sudo apt install -y lighttpd php-cgi php-common php-sqlite3 php-xml php-json php-intl
sudo apt install -y sqlite3 cron

On CentOS/Fedora: (i’d personally stick with Debian but you do you)

sudo yum update -y
sudo yum install -y git curl wget bind-utils net-tools
sudo yum install -y lighttpd lighttpd-fastcgi php php-common php-cli
sudo yum install -y sqlite cronie

2. Create PiHole user and directories

# Create pihole user
sudo useradd -r -s /usr/sbin/nologin pihole

# Create necessary directories
sudo mkdir -p /etc/pihole
sudo mkdir -p /opt/pihole
sudo mkdir -p /var/www/html/adminCode language: PHP (php)

3. clone Pihole repository

cd /tmp
git clone --depth 1 https://github.com/pi-hole/pi-hole.git Pi-hole
cd Pi-holeCode language: PHP (php)

4. Install the core components

# Copy scripts
sudo cp -r /tmp/Pi-hole/advanced/Scripts /opt/pihole/
sudo cp -r /tmp/Pi-hole/automated\ install/basic-install.sh /opt/pihole/

# Set permissions
sudo chmod +x /opt/pihole/Scripts/*.shCode language: PHP (php)

Optional: Install FTL (Faster Than Light) DNS Engine

# Download FTL binary
cd /tmp
wget https://github.com/pi-hole/FTL/releases/latest/download/pihole-FTL-$(uname -m)-linux-gnu

# Install FTL
sudo mv pihole-FTL-* /usr/bin/pihole-FTL
sudo chmod +x /usr/bin/pihole-FTLCode language: PHP (php)

6. Create configuration files

Create /etc/pihole/setupVars.conf:

sudo tee /etc/pihole/setupVars.conf > /dev/null <<EOF
PIHOLE_INTERFACE=eth0
IPV4_ADDRESS=192.168.1.100/24
IPV6_ADDRESS=
QUERY_LOGGING=true
INSTALL_WEB_SERVER=true
INSTALL_WEB_INTERFACE=true
LIGHTTPD_ENABLED=true
CACHE_SIZE=10000
DNS_FQDN_REQUIRED=true
DNS_BOGUS_PRIV=true
DNSMASQ_LISTENING=single
WEBPASSWORD=
BLOCKING_ENABLED=true
PIHOLE_DNS_1=8.8.8.8
PIHOLE_DNS_2=8.8.4.4
EOFCode language: JavaScript (javascript)

Note: Replace 192.168.1.100/24 with your static IP and subnet, and eth0 with your network interface.

7. Configure dnsmasq

Create /etc/dnsmasq.d/01-pihole.conf:

sudo tee /etc/dnsmasq.d/01-pihole.conf > /dev/null <<EOF
addn-hosts=/etc/pihole/gravity.list
addn-hosts=/etc/pihole/black.list
addn-hosts=/etc/pihole/local.list

domain-needed
bogus-priv
no-resolv

server=8.8.8.8
server=8.8.4.4

interface=eth0
bind-interfaces

cache-size=10000
log-queries
log-facility=/var/log/pihole.log

local-ttl=2

conf-file=/etc/dnsmasq.d/02-pihole-dhcp.conf
conf-file=/etc/dnsmasq.d/05-pihole-custom-cname.conf
EOFCode language: JavaScript (javascript)

8. set up gravity (blocklist db)

# Create initial gravity database
sudo touch /etc/pihole/gravity.list
sudo touch /etc/pihole/black.list
sudo touch /etc/pihole/whitelist.txt
sudo touch /etc/pihole/regex.list

# Set ownership
sudo chown -R pihole:pihole /etc/piholeCode language: PHP (php)

9. configure web interface

# Clone web interface
cd /var/www/html
sudo git clone --depth 1 https://github.com/pi-hole/AdminLTE.git admin

# Set permissions
sudo chown -R www-data:www-data /var/www/htmlCode language: PHP (php)

Configure lighttpd —> create /etc/lighttpd/conf-available/15-pihole-admin.conf:

sudo tee /etc/lighttpd/conf-available/15-pihole-admin.conf > /dev/null <<EOF
\$HTTP["url"] =~ "^/admin/" {
    setenv.add-response-header = (
        "X-Pi-hole" => "The Pi-hole Web interface is working!",
        "X-Frame-Options" => "DENY"
    )
}

\$HTTP["url"] == "/admin" {
    url.redirect = ("" => "/admin/")
}
EOFCode language: PHP (php)

Enable the configuration:

sudo ln -s /etc/lighttpd/conf-available/15-pihole-admin.conf /etc/lighttpd/conf-enabled/
sudo lighttpd-enable-mod fastcgi
sudo lighttpd-enable-mod fastcgi-php
sudo service lighttpd restart

10. Create systemd Service for FTL

Create /etc/systemd/system/pihole-FTL.service:

sudo tee /etc/systemd/system/pihole-FTL.service > /dev/null <<EOF
[Unit]
Description=PiHole FTL
After=network.target

[Service]
Type=forking
ExecStart=/usr/bin/pihole-FTL
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOFCode language: JavaScript (javascript)

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable pihole-FTL
sudo systemctl start pihole-FTL

11. set up the pihole command

# Create symlink for pihole command
sudo ln -s /opt/pihole/Scripts/pihole /usr/local/bin/pihole
sudo chmod +x /usr/local/bin/piholeCode language: PHP (php)

12. update gravity (download blocklists)

pihole -g

and set admin password:

pihole -a -p

Told ya it’d be easy to install like a walk in the park :3

Post install configuration

1. Access the web interface

Navigate to:

  • http://pi-hole/admin or
  • http://your-ip/admin

Log in with the password you set during installation.

2. Configure your router

Set your router’s DNS server to your PiHole’s IP address. This varies by router, but typically:

  1. Log into your router’s admin interface
  2. Find DHCP or DNS settings
  3. Set Primary DNS to your PiHole’s IP (e.g., 192.168.1.100)
  4. Set Secondary DNS to a fallback (e.g., 8.8.8.8) or leave blank for PiHole only
  5. Save and reboot the router

3. Configure individual devices (alternative)

If you don’t want to change router settings, configure DNS on each device:

Windows:

  • Network Settings —> Change adapter options → Right-click adapter → Properties
  • Select “Internet Protocol Version 4 (TCP/IPv4)” → Properties
  • Use the following DNS server addresses: Enter your PiHole IP

macOS:

  • System Preferences → Network → Advanced → DNS
  • Add your PiHole IP using the + button

Linux:

  • Edit /etc/resolv.conf or use NetworkManager

iOS/Android:

  • WiFi Settings → Configure DNS → Manual → Add Server

4. Add additional blocklists

In the web interface:

  1. Go to “Group Management” → “Adlists”
  2. Add blocklist URLs (search for “pihole blocklists” for community recommendations)
  3. Update gravity: pihole -g

Popular blocklist sources:

5. Whitelist/Blacklist domains

Via Web Interface:

  • Tools → Query Log (to find domains to whitelist)
  • Blacklist/Whitelist → Add domains

If you wanna do this via cmd:

# Whitelist
pihole -w example.com

# Blacklist
pihole -b ads.example.com

# Regex whitelist
pihole --regex-whitelist ".*\.example\.com$"

# Regex blacklist
pihole --regex-blacklist "^ad[sx]?[0-9]*\."Code language: PHP (php)

Set up local DNS records

Add local DNS entries for devices on your network:

# Edit local DNS file
sudo nano /etc/pihole/custom.list

# Add entries in format: IP_ADDRESS HOSTNAME
192.168.1.10 myserver.local
192.168.1.20 printer.localCode language: PHP (php)

and then restart:

pihole restartdns

Troubleshooting

Pihole not blocking Ads?

  1. Check DNS is pointing to PiHole:
nslookup pi.holeCode language: CSS (css)

2. Verify FTL is running:

sudo systemctl status pihole-FTL

3. Check gravity database:

pihole -g

4. Flush DNS cache on client devices

Web interface not accessible

  1. Check lighttpd status:
sudo systemctl status lighttpd

2. Restart lighttpd:

sudo systemctl restart lighttpd

3. Check firewalls

sudo ufw allow 80/tcp
sudo ufw allow 53/tcp
sudo ufw allow 53/udp

DNS resolution issues

  1. Check upstream DNS servers:
pihole -c -e

2. Test DNS resolution

dig @127.0.0.1 google.comCode language: CSS (css)

3. Check dnsmasq configuration:

pihole-FTL dnsmasq-test

Reset admin password:

pihole -a -p

Update pihole:

pihole -up

Wanna uninstall it? Messed something up?

Just nuke your entire OS hehe :3

Well you can but what would be the point of this whole ass blog post?

fine.

pihole uninstall

View logs:

# Real-time query log
pihole -t

# FTL log
sudo cat /var/log/pihole-FTL.log

# Web server log
sudo cat /var/log/lighttpd/error.logCode language: PHP (php)

Check pihole status:

pihole status

Restart pihole services:

# Restart DNS
pihole restartdns

# Restart FTL
sudo systemctl restart pihole-FTL

# Restart web server
sudo systemctl restart lighttpdCode language: PHP (php)

Useful commands

# Update gravity (blocklists)
pihole -g

# Enable/disable blocking
pihole enable
pihole disable [time]

# Tail the query log
pihole -t

# Check version
pihole -v

# Update PiHole
pihole -up

# Reconfigure
pihole -r

# Uninstall
pihole uninstall

# Debug
pihole -d

# Whitelist/Blacklist
pihole -w domain.com
pihole -b domain.com

# Query database
pihole -q domain.com

# Chronometer (live stats)
pihole -cCode language: PHP (php)

Additional resources


! Note !: Always ensure you have a backup DNS solution in case PiHole goes down. Consider setting up a secondary PiHole or having a backup DNS server configured on critical devices.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *