If you are someone that hates ads and telemetry a Pi-hole is a just must have. It is a network wide ad blocker that intercepts DNS queries and filters out unwanted domains.

How does it work?#

A DNS (Domain Name Server) is a server that allows devices to translate human readable names (domains) such as youtube.com into IP addresses, think of it as sort of a yellow pages book. So when webpages want to display ads using a provider such as google AdSense, they usually contact the providers using their domain names such as ad.doubleclick.net.

What a Pi-Hole does is it behaves as a DNS server locally, and when a computer queries a domain it checks against a blacklist if that query should be answered correctly. If it is a domain that is not in our blacklist it queries an upstream DNS and responds to the original query with the correct information. But if the domain happens to be in our blacklist it does not forward the query, it responds with an IP address leading to a blank page.

You can use the command nslookup to see what the DNS replies with, like what it shows when a domain is in the blacklist: !Image Description

How can we set up our Pi-Hole?#

I chose to setup my primary Pi-Hole instance in a docker container on my raspberry pi.

It is good practice to assign a static IP address to the machine ,or whatever device you choose to host it on.

If you chose to do the same you can create a docker-compose.yml .

# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      # DNS Ports
      - "53:53/tcp"
      - "53:53/udp"
      # Default HTTP Port
      - "80:80/tcp"
      # Default HTTPs Port. FTL will generate a self-signed certificate
      - "443:443/tcp"
      # Uncomment the below if using Pi-hole as your DHCP Server
      #- "67:67/udp"
      # Uncomment the line below if you are using Pi-hole as your NTP server
      #- "123:123/udp"
    environment:
      # Set the appropriate timezone for your location from
      # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones, e.g:
      TZ: 'Europe/London'
      # Set a password to access the web interface. Not setting one will result in a random password being assigned
      FTLCONF_webserver_api_password: 'correct horse battery staple'
      # If using Docker's default `bridge` network setting the dns listening mode should be set to 'all'
      FTLCONF_dns_listeningMode: 'all'
    # Volumes store your data between container upgrades
    volumes:
      # For persisting Pi-hole's databases and common configuration file
      - './etc-pihole:/etc/pihole'
      # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most starting fresh with Pi-hole v6. If you're upgrading from v5 you and have used this directory before, you should keep it enabled for the first v6 container start to allow for a complete migration. It can be removed afterwards. Needs environment variable FTLCONF_misc_etc_dnsmasq_d: 'true'
      #- './etc-dnsmasq.d:/etc/dnsmasq.d'
    cap_add:
      # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities
      # Required if you are using Pi-hole as your DHCP server, else not needed
      - NET_ADMIN
      # Required if you are using Pi-hole as your NTP client to be able to set the host's system time
      - SYS_TIME
      # Optional, if Pi-hole should get some more processing time
      - SYS_NICE
    restart: unless-stopped

Then to deploy it: docker compose up -d

HINT: To check on how the container is running you can use the command docker logs pihole

You can now set your network’s router to use this machine as the preferred DNS server. Or you could change your specific computer’s settings to use it as a DNS server so the changes do not affect your entire network.

But what if the machine running Pi-Hole goes offline? the machines on the network won’t be able to get responses to their DNS queries. To reduce this single point of failure I chose to run another instance of Pi-Hole in an LXC container in my proxmox machine.

Setting up a secondary Pi-Hole instance#

You could follow the previous section to setup another instance. But for my situation I decided to use a proxmox LXC container running Debian. To do this I:

  1. Assigned the container a static IP address
  2. SSH into the container and ran curl -sSL https://install.pi-hole.net | bash
  3. Configured it identically to my primary instance.

But now we run into another problem, well more of a nuisance, if we change the settings on the primary instance we have to go through the trouble of performing the same changes on the secondary instances. To solve this issue I decided to use nebula-sync, which is a project that performs synchronization actions

Deploying Nebula Sync in Docker#

To deploy nebula sync in the form of a docker container we create a file called compose.yml which contains:

services:
  nebula-sync:
    image: ghcr.io/lovelaze/nebula-sync:latest
    container_name: nebula-sync
    environment:
    - PRIMARY=http://ph1.example.com|password
    - REPLICAS=http://ph2.example.com|password,http://ph3.example.com|password
    - FULL_SYNC=true
    - RUN_GRAVITY=true
    - CRON=0 * * * *

Then deploy it using the command: docker compose up -d

HINT: To check on how the container is running you can use the command `docker logs nebula-sync

Bonus feature: Custom DNS records#

Since we have our own local DNS server we can map names such as printer.lan, nas.lan or minecraft.local to local IP addresses making them easier to remember and access.

You can set these records in the Pi-hole web UI under: Settings > DNS > Local DNS Records

Final thoughts:#

With dual Pi-Holes, plus Nebula Sync keeping everything in sync, your DNS infrastructure is more resilient, private, and powerful.

You now have:

  • Network-wide ad/tracker blocking
  • Redundancy for reliability
  • Automated syncing of blocklists and configs
  • Custom local DNS records

It’s a weekend project that pays off every single day. Happy self-hosting!

!Image Description