This is part of my series on renovating my homelabs to ring in the roaring ’20s.

I previously wrote about setting up a WireGuard VPN that allows me to connect to my LAN while offsite. Today I’ll be setting up a PiHole and explaining how I can use this to block ads for any device on my network or connected to my VPN.

Sections:


Setting up the PiHole

I’m going to run my PiHole as a containerized process, using the official Docker image. But, I’m running Fedora 31 - which uses cgroups v2 (that Docker doesn’t support) - so I’ll be using Podman. You can read more about that in my (upcoming) post on F31, cgroups v2, and Kubernetes.

The pattern I’ll be using is the officially recommended container lifecycle for Podman workflows:

  1. create the container with podman
  2. manage the runtime with systemd

The first thing we need to is translate their official docker-compose example in to a podman compatible script. For reference, here that is:

# pihole-docker-compose.yaml
version: "3"
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "67:67/udp"
      - "80:80/tcp"
      - "443:443/tcp"
    environment:
      TZ: 'America/Chicago'
    volumes:
      - './etc-pihole/:/etc/pihole/'
      - './etc-dnsmasq.d/:/etc/dnsmasq.d/'
    restart: unless-stopped

Which I turned in to the following pihole-create.sh script which creates the container using podman:

#!/bin/bash
podman create \
        --name pihole \
        -p 53:53/tcp \
        -p 53:53/udp \
        -p 67:67/udp \
        -p 80:80/tcp \
        -p 443:443/tcp \
        -e TZ=America/Chicago \
        -v /etc/pihole/pihole:/etc/pihole:z \
        -v /etc/pihole/dnsmasq.d:/etc/dnsmasq.d:z \
        pihole/pihole:latest

As we can see, the podman syntax is mostly equivalent to the docker CLI syntax. In fact, we could alias docker=podman and use it just like we use Docker, most of the time ;)

Notice we are not running the PiHole yet, we’ve just created the container. To start it (and manage it for the future), we use systemd.

I wrote this straightforward systemd unit to run the container:

# /etc/systemd/system/pihole.service
[Unit]
Description=PiHole Podman container
Wants=syslog.service

[Service]
Restart=always
ExecStart=/usr/bin/podman start -a pihole
ExecStop=/usr/bin/podman stop -t 10 pihole

[Install]
WantedBy=multi-user.target

And we can now enable and start the containerized PiHole process:

$ sudo systemctl enable --now pihole

Using enable instead of just start means the service will be started automatically at boot, so I never have to think about it again.

Note: one drawback that I have noticed with running PiHole with Podman is that all of the incoming requests have their source IP rewritten to the Podman NAT source 10.88.0.1. I have tried to turn of NATing with no luck. Further, PiHole will not run with --net=host on my machine because I have left “predictable interface names” enabled and it is hardcoded to expect eth0 - my primary interface is eno1. This does not affect usability and is only cosmetic (PiHole’s metrics show al requests come from 10.88.0.1 instead of my real clients) so I left it alone.

Finally, let’s check that the PiHole is running and blocking ad-related domains as expected:

$ dig doubleclick.net @192.168.0.53

; <<>> DiG 9.11.13-RedHat-9.11.13-3.fc31 <<>> doubleclick.net @192.168.0.53
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7561
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;doubleclick.net.               IN      A

;; ANSWER SECTION:
doubleclick.net.        2       IN      A       0.0.0.0

;; Query time: 0 msec
;; SERVER: 192.168.0.53#53(192.168.0.53)
;; WHEN: Sat Jan 11 13:52:45 CST 2020
;; MSG SIZE  rcvd: 60

Note in the ANSWER SECTION that the name resolved to 0.0.0.0. That means everything is working!

Using the PiHole for DNS

Now that we’re running an ad-blocking DNS server, we need to configure our other devices to use it and enjoy the ad-free Internet.

We can easily make it so that any device on the LAN uses the PiHole, and also enable it on all of the devices on our WireGuard mesh. I’ll start with applying it to my LAN.

Configuring the Gateway/Router

To route DNS traffic through the PiHole for all devices on the same network as it, we set the LAN DHCP server to use the PiHole as its DNS server. Unless you have configured it otherwise, the LAN DHCP server is usually the Gateway or Router provided by your ISP, or that is attached to the pipe from your ISP and exposed to the Internet.

The DHCP server is responsible for a things like assigning IP addresses to all the clients on your LAN. It also tells those clients what DNS servers they should use.

My Gateway has a couple of options for configuring the DNS server settings. By default, it’s set to “ISP”, meaning when my router just passes along the DNS server IPs from my ISP to its DHCP clients. I changed this to “Static”, and then put in my PiHole’s IP address as the primary DNS server.

In case the PiHole service crashes, I set a backup DNS IP to 1.1.1.1, Cloudflare’s public DNS server, so that the devices on my network won’t experience any interruptions to their network access.

Then, I told the gateway to renew all the client DHCP leases so that all my clients immediately picked up the new DNS settings. If your router does not have a function for that, you can disconnect and reconnect a device to the network to force it to get a new lease, or you can wait until the DHCP leases expire and are automatically renewed. This usually happens within 24 hours.

Once the clients have renewed their DHCP, they get the new DNS config pointed at the PiHole and start sending their DNS requests to it.

Configuring off-network WireGuard clients

I previously wrote about setting up LAN peering with WireGuard, which makes using the PiHole from off-network clients easy. I want to use the PiHole from my smartphone while not connected to my WiFi, or from my laptop while traveling - both are possible as long as I can establish a WireGuard tunnel.

To set this up, I need to make a small change in the /etc/wireguard/wg0.conf on my laptop:

[Interface]
# Name = wg0
...
DNS = 192.168.0.53 # <- this is the LAN IP address of my PiHole

[Peer] 
# this is a Peer on my PiHole's LAN - not necessarily the same machine
# if not, it needs to be configured to do IP forwarding and masquerading
...
AllowedIPs = 172.16.4.1, 192.168.0.0/24 # <- this is my LAN CIDR

After this change whenever I connect my WireGuard VPN this peer will send its DNS requests through my PiHole.

To set it up on my phone I make the same changes: adding the PiHole IP address to the DNS field of the WireGuard config, and making sure that routing Peer has the LAN CIDR in it’s AllowedIPs.

Now all of my devices are forever ad-free, at home or on the go!


Read the other articles in this series here: