Pihole is a network wide ad blocker. Using DHCP we can tell every device on your network to automatically and transparently use Pihole for DNS. But what if you have custom DNS entries in your firewall? I use OPNsense but this process largely transposes to PFsense as well.

When your laptop makes a DNS request, it is sent to Pihole. Pihole performs a lookup and if it can't find the requested address, forwards that request on to the next DNS server in the chain. If OPNsense has that DNS record in it's lookup caches or DNS configurations it will return it to the client. If not, it will go out to the upstream DNS provider (cloudflare or google or your ISP) and find it there instead. This process repeats until an authoritative DNS server is found for the requested lookup.

This allows you to use Pihole in conjunction with Unbound and perform network-wide ad-blocking but also retain complete custom local DNS control.

For the purposes of this post the following hostnames and IPs are used.

Hostname IP Address
firewall.ktz.lan 192.168.1.254
pihole.ktz.lan 192.168.1.97

OPNsense DHCP configuration

First we need to tell every device on our network to use Pihole for DNS.

Services -> DHCPv4 -> [LAN]

The next time a device requests an IP via DHCP it will now also receive instructions to use 192.168.1.97 for DNS.

Pihole DNS configuration

Next, we need to tell Pihole where to look when it doesn't know the answer. We want to send these requests to OPNsense, not the internet (yet).

One other thing you might wish to enable is Conditional Forwarding.

Without this it will look like all DNS requests came from your firewall and not each individual client.

OPNsense DNS configuration

Under System -> Settings -> General -> Networking set your public upstream DNS providers. I used Cloudflare 1.1.1.1 and Google 8.8.8.8 but you can use whatever you like.

Conclusion

To test everything works as you'd like, create a DNS entry in Unbound on OPNsense under Services -> Unbound DNS -> Overrides. In my case I created blogtest.ktz.lan to point to 1.2.3.4. Use dig to verify.

alex@stan ~ % dig blogtest.ktz.lan +short
1.2.3.4

alex@stan ~ % dig blogtest.ktz.lan

; <<>> DiG 9.10.6 <<>> blogtest.ktz.lan
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22032
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;blogtest.ktz.lan.		IN	A

;; ANSWER SECTION:
blogtest.ktz.lan.	3596	IN	A	1.2.3.4

;; Query time: 4 msec
;; SERVER: 192.168.1.97#53(192.168.1.97)
;; WHEN: Wed May 20 13:34:20 EDT 2020
;; MSG SIZE  rcvd: 61

Success! Digs output can be a little cryptic but note the SERVER output is 192.168.1.97, which is our Pihole. However the custom entry is in Unbound on OPNsense so by this logic Pihole must have sent our DNS request on to OPNsense and returned the value we set. Proof it works! Huzzah!