Pi-hole and Internal Domain

Information

I’m using Pi-hole, which acts as a DNS sinkhole, for blocking ads and malicious domains, and as an internal DNS server. For the upstream DNS, I’m using Unbound, a recursive and caching DNS resolver that resolves names directly with the root DNS servers.

The benefits to me of using Pi-hole with Unbound:

  • Lightweight: runs smoothly with few requirements
  • Blocklists: there are lots of pre-made blocklists out there, or I can put together custom lists that suit my preferences and HomeLab needs
    • Block known malicious domains to add an extra layer of security and reduce the risk of sensitive information being compromised
    • Block ads, trackers and all that kind of unwanted content at the network level
    • A cleaner, faster and safer browsing experience across all my home devices
    • It can potentially speed up browsing and reduce bandwidth usage by reducing the amount of data my network has to process
  • Custom DNS Entries: I can manage my local DNS records for the internal services, so I can access them via friendly domain names instead of IP addresses
  • Monitoring: The web interface is pretty neat, allowing me to monitor DNS queries, blocked requests and network traffic. It also offers great CLI commands and detailed logs, making it quite easy to integrate with my Zabbix monitoring
  • Automation: Pi-hole has an API that makes it easy to create and manage DNS records
  • Integrating Unbound: It’s great for privacy and control because I can query the root DNS servers directly, without going through third-party DNS services
  • Open-source software

I use .internal in my HomeLab because other often used internal TLDs like .local, .home, .lab all have some disadvantages or are not recommended because it is simply possible that they will become public TLDs in the future.

Configuration

Now to my latest modifications, I have added the internal names to the Pi-hole custom list config and made sure that no internal names are leaked to external DNS servers. The internal records are stored in /etc/pihole/custom.list and can be modified using the Web UI or the CLI.

Note: Pi-hole checks different locations for locally defined records, and only the first record triggers an address-to-name association. Here is the order:

  1. The device’s host name and pi.hole
  2. Configured in a config file in /etc/dnsmasq.d/
  3. Read from /etc/hosts
  4. Read from the “Local (custom) DNS” list (stored in /etc/pihole/custom.list)

I’ve got the latest Pi-hole version up and running on a Raspberry Pi with Raspbian 11, and I’ve put a second one on a virtual machine with Ubuntu 24.04 LTS.

  • Pi-hole v5.18.3
  • FTL v5.25.2
  • Web Interface v5.21

Setup local custom DNS - Web UI

  • https://<pi-hole-ip-address>/admin
  • Local DNS -> DNS Records
  • Enter the FQDN and IP address and click Add
    • e.g. example.internal and 192.168.0.10

You can view a list of all the records in the file on the same page.

Setup local custom DNS - CLI

Just use this command to add an item to the custom list: pihole -a addcustomdns 192.168.0.10 example.internal

Example list

/etc/pihole/custom.list

# Physical machines
192.168.0.2 pihole.internal
192.168.0.11 proxmox.internal

# Virtual machines
192.168.0.21 zabbix.prod.internal
192.168.0.31 gitea.internal

# Container
192.168.0.221 zabbix.dev.internal

Prevent DNS leak

To stop internal names being leaked to public DNS servers (which are defined as upstream servers, or in my case with Unbound, the DNS root servers), we can modify the dnsmasq config. Pi-hole makes use of dnsmasq as a light-weight DNS server.

  • Open or create a local dnsmasq configuration file: vim /etc/dnsmasq.d/02-local.conf
  • Add local domain (e.g. internal) and time-to-live:
    local=/internal/
    local-ttl=60
    
  • Verify dnsmasq syntax pihole-FTL dnsmasq-test
  • Restart Pi-hole DNS service sudo pihole restartdns

I tested the configuration with dig. Here’s the output before I added the DNS leak prevention: Take a look at the “AUTHORITY SECTION”

❯ dig test.internal

; <<>> DiG 9.18.28-0ubuntu0.22.04.1-Ubuntu <<>> test.internal
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 46083
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;test.internal.              IN      A

;; AUTHORITY SECTION:
.                       2622    IN      SOA     a.root-servers.net. nstld.verisign-grs.com. 2024082300 1800 900 604800 86400

;; Query time: 44 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Fri Aug 23 11:43:03 CEST 2024
;; MSG SIZE  rcvd: 120

After I made the changes to the dnsmasq configuration, it now looks like this:

❯ dig test.internal

; <<>> DiG 9.18.28-0ubuntu0.22.04.1-Ubuntu <<>> test.internal
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 10091
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;test.internal.              IN      A

;; Query time: 4 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Fri Aug 23 12:12:39 CEST 2024
;; MSG SIZE  rcvd: 45

Resources