Pi-hole as a DNS Server#

Pi-hole is a DNS server with a filter. When a device asks for a domain that sits on a blocklist, Pi-hole does not pass the query on to the upstream but answers it itself — with an address that leads nowhere. The advantage over a blocker in the browser is where the filtering happens: name resolution is used by every device on the network, including the TV and a visitor’s phone, neither of which lets you install anything.

The price is in the same sentence: a service every device relies on is also a service whose outage every device notices.

Prerequisites#

Installation#

curl -sSL https://install.pi-hole.net | bash

What that one-liner takes on trust, and how to separate downloading from running, is written up under Running scripts from the internet.

The script opens a dialog and asks, in order, for the interface, the upstream DNS, the blocklist it suggests, whether queries should be logged and in how much detail. At the end it prints a generated password for the web interface — the only moment it is visible in plain text. pihole setpassword sets your own.

What version 6 changed#

Guides written for Pi-hole 5 describe a substantially different system. What changed, and what keeps coming up while looking things up:

BeforeSince version 6
files copied straight into the systeminstalled as a Debian package pihole-meta that pulls its dependencies via apt
lighttpd and PHP for the web interfacea web server built into pihole-FTL, no extra service
setupVars.conf plus separate dnsmasq filesone configuration: /etc/pihole/pihole.toml
DNS and optionally DHCPan NTP server on top, active by default

What is listening afterwards:

ss -tulpn
PortFor
53/tcp, 53/udpDNS — the actual service
80/tcpweb interface, unencrypted
443/tcpweb interface over TLS, with a self-issued certificate
123/udpthe NTP server FTL brings along

All of it is answered by a single process, pihole-FTL. Anyone planning to run a web server or their own time service on the same machine later should keep that list in mind.

Reading the configuration#

Instead of digging through 70 KB of pihole.toml, query individual values:

pihole-FTL --config dns.upstreams      # one value
pihole-FTL --config                    # every value, flat

In the file itself every value is documented, and anything deviating from the default carries a ### CHANGED marker — so what makes this installation specific is visible at a glance, as opposed to what is simply the default.

The state after the install on the homelab box:

KeyValueMeaning
dns.interfaceenp1s0the interface answers are served on
dns.listeningModeLOCALonly queries from the local subnet are answered
dns.upstreams8.8.8.8, 8.8.4.4where queries that are not blocked go
dns.dnssecfalsesignatures on answers are not validated
dns.queryLoggingtrueevery query is logged along with its client
dns.hosts10.10.10.3 dns01.xlab.internala name of its own for the machine itself
dns.blocking.modeNULLblocked names are answered with 0.0.0.0
dhcp.activefalsethe router keeps handing out addresses

Two of these values have changed since: dns.upstreams points at Quad9 (Choosing an Upstream DNS), and the machine’s /etc/resolv.conf points at itself. The table describes the state right after the install.

listeningMode is the value that decides the attack surface. LOCAL answers queries from networks the machine itself has an address in and ignores the rest. ALL answers everything — on a machine that ever gets a public address, that turns it into an open resolver, ready to be abused for amplification attacks.

blocking.mode = NULL means a blocked domain resolves to 0.0.0.0. The client fails immediately instead of opening a connection to Pi-hole and hitting a block page there. That is faster and takes load off the service, but in a browser it produces a connection error rather than an explanation.

Values are changed through the same option:

sudo pihole-FTL --config dns.dnssec true

Editing pihole.toml by hand works too, followed by pihole reloaddns. FTL rewrites the file itself whenever something changes, though — your own comments in it will not survive.

Local names#

A DNS server on your own network can do more than filter: it can hand out names. Instead of memorising addresses, you record once which name points at which address — in the web interface under Settings → Local DNS Records, in the configuration it is dns.hosts:

pihole-FTL --config dns.hosts
[ 10.10.10.3 dns01.xlab.internal ]

The entries are written to /etc/pihole/hosts/custom.list in /etc/hosts format. The first name is the machine itself: dns01 for the role, xlab.internal as the zone for the homelab.

Picking the suffix is not a matter of taste:

SuffixSituation
.internalreserved by ICANN for private networks since 2024, and therefore permanently free of collisions with public domains
.localclaimed by mDNS (RFC 6762). Using it in DNS means arguing with Avahi and Bonjour over who gets to answer
.lan, .home, .boxcommon, but reserved nowhere. Works until somebody runs the suffix as a public TLD
a real domain of your ownclean and more work: the zone has to be maintained, and it is supposed to answer differently inside and outside

The entry only covers forward resolution. In reverse, dig -x 10.10.10.3 still gets pi.hole — the PTR Pi-hole assigns to itself (dns.piholePTR). Making both match means changing that value.

Not to be confused with dns.domain.name (default: lan). That domain is appended to names Pi-hole learns over DHCP — as long as the router runs DHCP, the value does not matter.

Verify#

The honest test does not come from the server itself but from another machine on the network:

dig +short @10.10.10.3 example.com           # normal answer, upstream works
dig +short @10.10.10.3 doubleclick.net       # 0.0.0.0 = blocked
dig +short @10.10.10.3 pi.hole               # 10.10.10.3, its own address
dig +short @10.10.10.3 dns01.xlab.internal   # 10.10.10.3, the entry of your own

On the server:

pihole status                           # is FTL listening on port 53?
systemctl status pihole-FTL
pihole -t                               # follow queries live

What is still open afterwards#

The install leaves a DNS server running. It does not leave it being used.

Nobody asks it. Right after the install the gateway keeps handing out its own address as the nameserver, so every query bypasses the filter. Only once 10.10.10.3 is set there — or Pi-hole takes over DHCP itself — does anything arrive. Done since: Handing Out Pi-hole via DHCP. Clients with a hardcoded DNS server, or with DNS-over-HTTPS in the browser, stay outside either way.

The server does not ask itself. Its /etc/resolv.conf still pointed at the router and a public resolver — which made the machine running the filter the only one on the network not using it. nameserver 127.0.0.1 belongs there. Done since: The DNS Server Asks Itself.

The worry that the change might vanish on the next ifup turned out to be unfounded: nothing generates that file on this machine — despite the misleading comment in its header (Pitfall: dns-nameservers without resolvconf).

The upstream is a decision, not a default. The installer suggests Google, and confirming it builds a filter that still reports every single query to Google — just bundled behind one address. Quad9 sits there now; the trade-offs between providers and the path to a recursive resolver of your own are under Choosing an Upstream DNS.

DNSSEC is off. Upstream answers are taken as they come. Turning it on costs a few milliseconds per query and breaks on domains with broken signatures — which then looks like a Pi-hole outage. Deliberately left that way while the upstream validates on its own; the reasoning and the test commands are under DNSSEC: deliberately not yet.

The web interface sits on port 80. Reachable across the LAN, unencrypted, with a password in front of it. The TLS certificate on 443 is self-issued, so every browser warns about it.

Day-to-day commands#

CommandEffect
pihole -upupdate Pi-hole (core, web, FTL)
pihole -gre-read the blocklists
pihole disable 5msuspend filtering for five minutes, back on automatically
pihole -tfollow queries live
sudo pihole -q domain.tldcheck which list a domain is on — without sudo the CLI asks for the web interface password
pihole setpasswordset the web interface password
pihole -rrepair, when something stops starting after an update

pihole disable without a duration turns filtering off indefinitely. Nobody notices — after all, everything works.