Build private DNS filtering with Pi-hole and Unbound
Layer Pi-hole v6 filtering on a client-facing DNS service and Unbound validation on a loopback-only recursive port, with explicit privacy, firewall, DNSSEC, canary, and rollback controls.
Provide private network DNS filtering without sending the complete query stream to a public recursive provider, while proving that Pi-hole owns client policy, Unbound owns recursion and DNSSEC validation, and neither layer becomes an open resolver.
- Ubuntu Server 24.04 LTS
- Pi-hole 6.x
- Unbound 1.19.x
- Dedicated stable host Use a maintained Linux host with a stable private address, supported architecture, at least 512 MB free RAM, and reliable local storage.
ip -brief address; free -h; df -h / /etc - Port ownership Confirm no required process owns TCP or UDP port 53 and nothing external uses loopback port 5335.
sudo ss -lntup | grep -E ':(53|5335)\b' || true - Network inventory Record the client CIDR, management CIDR, interface, DHCP or VPN owner, IPv6 resolver advertisement, and the current DNS addresses.
- Time and outbound DNS Ensure synchronized time and outbound UDP plus TCP port 53; Unbound validation and large-response fallback depend on them.
timedatectl status - Recovery path Keep console or equivalent recovery access and a tested way to restore previous client DNS before editing firewall or DHCP settings.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A two-process DNS appliance in which Pi-hole FTL serves managed clients on {{piHoleAddress}} port 53 and Unbound performs recursion only on loopback port 5335.
- A filtering policy with a deterministic temporary fixture, documented privacy level, protected administration path, and query behavior that can be tested without relying on an advertisement domain remaining in a public list.
- A DNSSEC boundary in which Unbound performs validation exactly once, accepts correctly signed data, rejects a deliberately bogus chain, and never exposes its recursive port to the network.
- A staged DHCP or VPN migration with IPv4 and IPv6 bypass checks, layer-specific diagnostics, configuration backup, and a tested method to restore previous DNS.
- You can identify whether a failed query was blocked by policy, failed at Pi-hole, failed between Pi-hole and Unbound, failed DNSSEC validation, or could not reach authoritative DNS.
- You can explain why 127.0.0.1#5335 is an internal service boundary rather than a client resolver address and why port 5335 must never be opened in the firewall.
- You can prove filtering with an owned test entry, then remove that fixture so acceptance testing does not leave unexplained production policy.
- You can choose the privacy-versus-diagnostics trade-off consciously and avoid treating a dashboard with client names as harmless operational data.
- You can roll out and roll back DNS without advertising an unrelated public resolver that silently bypasses local filtering and names.
Architecture
How the parts fit together
Clients contact Pi-hole FTL on a stable private address. FTL answers local and blocked policy decisions and forwards allowed misses to Unbound at 127.0.0.1#5335. Unbound iterates through public authority, validates DNSSEC from the root trust anchor, caches accepted data, and returns it to FTL. Only FTL crosses the network boundary.
- An approved client asks Pi-hole on {{piHoleAddress}} port 53. LOCAL listening mode and firewall policy reject sources outside the administered local boundary.
- FTL evaluates local records, exact allow/deny rules, regex rules, group policy, and gravity data. A blocked query is answered according to the configured blocking mode without reaching Unbound.
- An allowed cache miss is forwarded only to 127.0.0.1#5335. No external recursive provider receives the complete stream from Pi-hole.
- Unbound follows delegations using UDP or TCP, validates signed data from its root anchor, caches accepted answers, and returns SERVFAIL for a bogus signed chain.
- FTL returns the permitted result and records only the detail allowed by {{privacyLevel}}. Layer-specific direct queries are reserved for diagnosis and never become alternate client paths.
Assumptions
- The example uses Pi-hole v6 on Ubuntu Server 24.04 LTS with Unbound 1.19.x. Pi-hole configuration is managed through pihole-FTL --config rather than obsolete v5 setup variables.
- {{piHoleAddress}} is a stable private address on {{clientInterface}}. The host does not also provide required public authoritative DNS.
- {{clientCidr}} represents managed clients and {{managementCidr}} is a narrower administration range. Guest and public networks are excluded.
- Pi-hole and Unbound run directly on the same host. Container-specific environment precedence, namespaces, and port publication require a different design.
- The operator can restore old DNS advertisements and access the host if a firewall or resolver mistake breaks remote name resolution.
- Outbound UDP and TCP port 53 plus synchronized time are available. The guide does not conceal a forced corporate DNS forwarder behind a claim of direct recursion.
- IPv6 client DNS is either disabled intentionally or managed with equivalent Pi-hole addresses and firewall policy. Ignoring RA and DHCPv6 can create a complete filtering bypass.
Key concepts
- Filtering resolver
- A client-facing DNS service that applies local allow, deny, group, and response policy before forwarding accepted questions. Filtering is a policy decision, not cryptographic validation.
- Recursive backend
- The Unbound service that discovers answers by following the DNS hierarchy. It listens only on loopback because Pi-hole is its sole intended client.
- Single validation owner
- A topology in which one clearly identified layer performs DNSSEC validation. Here Unbound validates and Pi-hole's own DNSSEC switch remains false, making faults and telemetry easier to interpret.
- Blocked answer
- A locally synthesized response such as 0.0.0.0, NODATA, or NXDOMAIN produced because policy matched. It is not an upstream DNS answer and should be tested separately from DNSSEC.
- LOCAL listening mode
- Pi-hole's safe default that accepts requests only from addresses on locally connected subnets. It complements, but does not replace, a source-restricted firewall.
- Gravity
- Pi-hole's compiled database of subscribed domains and policy relationships. A successful download is not enough; the rebuild, swap, reload, and sample match must all be verified.
- Privacy level
- FTL's setting controlling whether domains and clients appear in statistics. Higher privacy reduces retrospective diagnosis, so choose it as a data-governance decision.
- DNS bypass
- Any resolver path clients can use without Pi-hole, including a public secondary, hard-coded application DNS, encrypted DNS, or an IPv6-advertised server.
- Layer isolation
- Testing Pi-hole and Unbound independently to identify the failing boundary. Direct Unbound tests are for diagnosis; clients must not be configured to use port 5335.
Before you copy
Values used in this guide
{{piHoleAddress}}Stable private IPv4 address presented to DNS clients.
Example: 10.24.0.54{{clientCidr}}Managed client range permitted to query Pi-hole.
Example: 10.24.0.0/24{{managementCidr}}Narrow administrator range permitted to reach the web interface.
Example: 10.24.0.0/28{{clientInterface}}Interface that owns piHoleAddress and receives managed client traffic.
Example: ens18{{privacyLevel}}Approved Pi-hole FTL privacy level after operational and data review.
Example: 2{{testBlockedName}}Reserved test-domain name temporarily added to the exact denylist for acceptance.
Example: telemetry.example.testSecurity and production boundaries
- Filtering can reduce accidental connections but is not a malware boundary. Applications can use direct addresses, alternate resolvers, encrypted DNS, proxies, or allowed domains. Keep endpoint and network controls.
- Pi-hole query data can reveal individuals, devices, applications, health interests, and incident activity. Restrict the dashboard, database, backups, logs, and metrics according to an explicit retention policy.
- Never set listeningMode ALL merely to fix a routing problem. It allows all origins at the application layer and can turn a public interface into an amplification service.
- Unbound port 5335 is internal. Binding it to 0.0.0.0 or adding a firewall rule for clients bypasses Pi-hole policy and creates a second recursive surface.
- The web administration surface changes network-wide policy. Limit it to {{managementCidr}}, use strong authentication, maintain updates, and prefer a trusted management path rather than public exposure.
- Review subscribed lists for provenance, false positives, licensing, update continuity, and takeover risk. More domains is not automatically better protection.
- The official installer is downloaded code executed as root. Saving and reviewing it, checking the effective URL, and using only the official endpoint reduces but does not eliminate supply-chain risk.
Stop before continuing if
- Stop if the host address is dynamic, port 53 belongs to a required service, or port 5335 is exposed beyond loopback.
- Stop if current DHCP, VPN, IPv6 RA, or static DNS cannot be restored quickly.
- Stop if time is unsynchronized or outbound TCP DNS is blocked.
- Stop if installer review or final URL indicates anything other than the official Pi-hole endpoint.
- Stop if Unbound positive and deliberately broken DNSSEC tests do not both behave as expected.
- Stop if an unapproved source receives a recursive answer or the web interface is reachable outside {{managementCidr}}.
- Stop rollout if the canary uses another resolver over IPv4, IPv6, application DoH, or VPN policy.
decision
Freeze the two-layer DNS topology
Assign Pi-hole FTL the client-facing {{piHoleAddress}} port 53 and Unbound only 127.0.0.1 port 5335. Define clients, management sources, current DNS, and a canary group before installing either layer.
Why this step matters
The two services deliberately have different trust and exposure boundaries. Naming each socket and audience before installation prevents a later 'temporary' port change from bypassing filtering or exposing recursion.
What to understand
Pi-hole is the only client-facing DNS service. Unbound is an internal backend, not a secondary address for laptops or routers.
LOCAL mode uses connected subnets, while the firewall uses the explicit {{clientCidr}}; both should agree but provide independent protection.
Record IPv6 advertisements even in an IPv4 example. A client may prefer an IPv6 resolver and never send the query to Pi-hole.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- No service state changes; records socket ownership, access ranges, provisioning ownership, current DNS, and rollback.
Syntax explained
127.0.0.1:5335- Restricts the recursive backend to processes on the same host and avoids client-facing port 53.
grep -E ':(53|5335)\b'- Checks both planned ports for conflicting TCP or UDP listeners.
Values stay on this page and are never sent or saved.
printf 'Pi-hole: %s:53\nUnbound: 127.0.0.1:5335\nClients: %s\nManagement: %s\n' '{{piHoleAddress}}' '{{clientCidr}}' '{{managementCidr}}'; sudo ss -lntup | grep -E ':(53|5335)\b' || truePi-hole: 10.24.0.54:53 Unbound: 127.0.0.1:5335 Clients: 10.24.0.0/24 Management: 10.24.0.0/28
Checkpoint: Approve the layered DNS boundary
Continue whenThe address is stable, both ports are free, CIDRs are correct, IPv6 is accounted for, and old DNS is recorded.
Stop whenAnother required service owns a port, the address is dynamic, or client provisioning cannot be reversed.
If this step fails
The “Approve the layered DNS boundary” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: The address is stable, both ports are free, CIDRs are correct, IPv6 is accounted for, and old DNS is recorded.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Another required service owns a port, the address is dynamic, or client provisioning cannot be reversed.
Security notes
- Never solve a conflict by binding Unbound to a public address or permitting clients to port 5335.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use separate hosts when stronger isolation or independent resource scaling is required.
Stop conditions
- Do not install until every path in the topology has one clear owner.
command
Download, inspect, and run the official Pi-hole installer
Fetch the installer to a file, verify that the final URL remains the official Pi-hole host, read the script, and only then execute it interactively. Select {{clientInterface}}, {{piHoleAddress}}, and a temporary upstream that will be replaced after Unbound passes.
Why this step matters
The installer performs privileged package and configuration changes. Downloading it first permits source and effective-URL inspection, unlike piping an unexamined network response directly into a root shell.
What to understand
curl failure options reject HTTP errors and incomplete transfers; url_effective reveals an unexpected redirect before execution.
The interactive choices establish address and interface assumptions. Capture them because later pihole-FTL configuration must agree.
Review the install log for warnings and record the Pi-hole Core, Web, and FTL versions after completion.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Installs Pi-hole packages and dependencies, creates /etc/pihole state, enables FTL and web services, and starts a port 53 listener.
Syntax explained
--fail --show-error- Makes HTTP failures visible and prevents a successful shell exit on an error page.
--location --write-out- Follows approved redirects while printing the final effective URL and status.
curl --fail --silent --show-error --location --output /tmp/pihole-basic-install.sh --write-out 'final=%{url_effective}\nhttp=%{http_code}\n' https://install.pi-hole.net && less /tmp/pihole-basic-install.sh && sudo bash /tmp/pihole-basic-install.shfinal=https://install.pi-hole.net/ http=200 [✓] Root user check [✓] Disk space check [✓] Update local cache of available packages [✓] FTL is listening on port 53 [i] The install log is located at: /etc/pihole/install.log
Checkpoint: Accept an official installed Pi-hole
Continue whenThe effective URL is official, script review is complete, FTL is active, and versions plus install log are captured.
Stop whenThe redirect leaves pi-hole.net, review finds unexpected behavior, or an existing DNS service is displaced.
If this step fails
The “Accept an official installed Pi-hole” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: The effective URL is official, script review is complete, FTL is active, and versions plus install log are captured.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. The redirect leaves pi-hole.net, review finds unexpected behavior, or an existing DNS service is displaced.
Security notes
- Delete the temporary installer after retaining an approved digest; do not repeatedly run a changing network script as troubleshooting.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use the Pi-hole-supported package or container deployment method with equivalent version, volume, port, and environment review.
Stop conditions
- Do not continue if the administration credential or recovery method is unknown.
command
Install the local validating resolver
Install Unbound and diagnostic tools from the distribution, record its modules, and initialize the root trust anchor under the service account.
Why this step matters
A packaged Unbound instance supplies the recursive and validator modules without giving a public provider the entire allowed query stream. The anchor must be usable by its service identity.
What to understand
Record the exact module list; an executable named unbound does not prove the validator or iterator is linked.
Initialization as user unbound catches directory and confinement errors before FTL depends on the backend.
Accurate time is part of this checkpoint because signature inception, expiry, and trust-anchor rollover are time-dependent.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Installs Unbound, anchor tooling, dig, a systemd unit, service account state, and /var/lib/unbound/root.key.
Syntax explained
unbound-anchor -a- Initializes or updates the auto-managed root trust-anchor file at the specified path.
unbound -V- Prints version, modules, libraries, and build characteristics for evidence.
sudo apt update && sudo apt install --yes unbound unbound-anchor dnsutils && sudo install -d -o unbound -g unbound -m 0755 /var/lib/unbound && sudo -u unbound unbound-anchor -a /var/lib/unbound/root.key && unbound -V | sed -n '1,8p'Version 1.19.2 linked modules: dns64 respip validator iterator configured for x86_64-pc-linux-gnu /var/lib/unbound/root.key has content
Checkpoint: Accept the validating backend build
Continue whenUnbound 1.19.x includes validator and iterator, root.key is service-readable, and time is synchronized.
Stop whenPackage origin, build modules, permissions, or time are wrong.
If this step fails
The “Accept the validating backend build” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: Unbound 1.19.x includes validator and iterator, root.key is service-readable, and time is synchronized.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Package origin, build modules, permissions, or time are wrong.
Security notes
- Do not import a trust anchor from an arbitrary tutorial mirror to make validation errors disappear.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use an approved separately managed recursive resolver and document the changed privacy and trust boundary.
Stop conditions
- Do not configure Pi-hole upstream until Unbound passes independent tests.
config
Configure Unbound as a loopback-only recursive backend
Bind Unbound to 127.0.0.1 port 5335, validate from the root anchor, keep UDP and TCP enabled, minimize query names, and refuse non-loopback clients by construction.
Why this step matters
Loopback binding makes Pi-hole the only process able to ask Unbound for recursion. Explicit validation and transport settings keep the backend behavior auditable and avoid an accidental second network resolver.
What to understand
Port 5335 avoids Pi-hole's client-facing port 53 while retaining standard DNS protocol semantics.
edns-buffer-size 1232 reduces fragmentation risk but does not remove the requirement for TCP fallback.
do-ip6 false applies to upstream transport in this example; it is not a substitute for examining IPv6 DNS advertised to clients.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Creates pi-hole.conf and changes Unbound listener, access, validation, privacy, transport, and cache behavior after restart.
Syntax explained
interface: 127.0.0.1- Prevents any directly routed client from reaching this backend.
port: 5335- Separates backend recursion from FTL's port 53.
auto-trust-anchor-file- Enables DNSSEC validation from maintained root-key state.
/etc/unbound/unbound.conf.d/pi-hole.confserver:
interface: 127.0.0.1
port: 5335
access-control: 127.0.0.0/8 allow
do-udp: yes
do-tcp: yes
do-ip4: yes
do-ip6: no
auto-trust-anchor-file: "/var/lib/unbound/root.key"
qname-minimisation: yes
harden-dnssec-stripped: yes
harden-glue: yes
harden-below-nxdomain: yes
aggressive-nsec: yes
prefetch: yes
prefetch-key: yes
hide-identity: yes
hide-version: yes
edns-buffer-size: 1232
verbosity: 1server: interface: 127.0.0.1 port: 5335 access-control: 127.0.0.0/8 allow auto-trust-anchor-file: "/var/lib/unbound/root.key" qname-minimisation: yes harden-dnssec-stripped: yes
Checkpoint: Review an internal-only validator
Continue whenOnly loopback is permitted, UDP and TCP are enabled, the anchor path is correct, and no forwarding resolver is configured.
Stop whenA wildcard interface, non-loopback access, missing TCP, or unreviewed forward-zone appears.
If this step fails
The “Review an internal-only validator” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: Only loopback is permitted, UDP and TCP are enabled, the anchor path is correct, and no forwarding resolver is configured.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. A wildcard interface, non-loopback access, missing TCP, or unreviewed forward-zone appears.
Security notes
- Treat changing the interface to 0.0.0.0 as a new exposed-service design requiring separate review, not as a connectivity fix.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Place Unbound on a separate protected host and allow only the Pi-hole address through a stateful firewall.
Stop conditions
- Do not restart while template braces or unknown directives remain.
verification
Validate, start, and test Unbound independently
Check the configuration, restart the service, prove that only loopback owns port 5335, and require positive plus deliberately broken DNSSEC behavior before connecting Pi-hole.
Why this step matters
Testing Unbound before FTL connects removes one layer from diagnosis. Listener, positive validation, and deliberate bogus failure together prove the internal backend contract.
What to understand
unbound-checkconf is a no-impact parser gate; systemctl active alone cannot prove the requested socket or validation state.
The AD flag on the positive response and SERVFAIL on the broken chain demonstrate fail-closed behavior.
The socket must show 127.0.0.1, never {{piHoleAddress}}, 0.0.0.0, or an external IPv6 address.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Restarts Unbound, opens loopback port 5335, initializes caches, and may update managed anchor state.
Syntax explained
-p 5335- Queries the nonstandard internal Unbound port explicitly.
+dnssec +comments- Retains validation flags and response status needed for acceptance.
sudo unbound-checkconf && sudo systemctl restart unbound && systemctl is-active unbound && sudo ss -lntup '( sport = :5335 )'; dig @127.0.0.1 -p 5335 cloudflare.com A +dnssec +noall +comments +answer; dig @127.0.0.1 -p 5335 dnssec-failed.org A +dnssec +noall +commentsunbound-checkconf: no errors in /etc/unbound/unbound.conf
active
udp UNCONN 0 0 127.0.0.1:5335 0.0.0.0:* users:(("unbound",pid=2281,fd=5))
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2
cloudflare.com. 300 IN A 104.16.133.229
;; status: SERVFAIL, id: 59001Checkpoint: Prove the backend in isolation
Continue whenParser passes, service is active, only loopback owns 5335, signed data has AD, and bogus data is SERVFAIL.
Stop whenAny listener, transport, or validation result differs.
If this step fails
The “Prove the backend in isolation” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: Parser passes, service is active, only loopback owns 5335, signed data has AD, and bogus data is SERVFAIL.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Any listener, transport, or validation result differs.
Security notes
- Do not add domain-insecure or disable validation to make the negative fixture answer.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use owned signed and intentionally broken fixture zones when external test names are prohibited.
Stop conditions
- Do not point FTL to an unverified backend.
command
Point Pi-hole only at the local Unbound backend
Configure the sole upstream as 127.0.0.1#5335, retain LOCAL listening mode, set the client-facing interface, and leave Pi-hole DNSSEC disabled because Unbound is the single validation owner.
Why this step matters
Pi-hole v6 stores effective settings through FTL configuration. A single loopback upstream makes the data path and failure ownership unambiguous, while disabling duplicate validation avoids contradictory caches and telemetry.
What to understand
The upstream value is an array even when it contains one server; #5335 is Pi-hole's host-port notation.
Environment variables can override TOML and make values read-only in container deployments. This host installation should still verify the read-back value.
LOCAL is safer than ALL and normally sufficient for directly connected managed networks; routed clients need an intentional policy rather than a blanket relaxation.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Changes Pi-hole upstream, interface, listening policy, and DNSSEC responsibility; FTL reloads affected runtime state.
Syntax explained
dns.upstreams '["127.0.0.1#5335"]'- Sets exactly one internal recursive destination and removes provider fallbacks.
dns.dnssec false- Leaves validation to Unbound, the documented single owner.
dns.listeningMode LOCAL- Accepts local-subnet origins rather than every reachable source.
Values stay on this page and are never sent or saved.
sudo pihole-FTL --config dns.upstreams '["127.0.0.1#5335"]' && sudo pihole-FTL --config dns.interface '{{clientInterface}}' && sudo pihole-FTL --config dns.listeningMode 'LOCAL' && sudo pihole-FTL --config dns.dnssec false && sudo pihole-FTL --config dns.upstreams && sudo pihole-FTL --config dns.listeningMode && sudo pihole-FTL --config dns.dnssec[ "127.0.0.1#5335" ] "LOCAL" false
Checkpoint: Read back the effective FTL path
Continue whenThe only upstream is 127.0.0.1#5335, listening is LOCAL, interface is correct, and Pi-hole DNSSEC is false.
Stop whenAn external upstream remains, a setting is overridden, or mode is ALL/NONE without a separate reviewed design.
If this step fails
The “Read back the effective FTL path” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: The only upstream is 127.0.0.1#5335, listening is LOCAL, interface is correct, and Pi-hole DNSSEC is false.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. An external upstream remains, a setting is overridden, or mode is ALL/NONE without a separate reviewed design.
Security notes
- A second public upstream is a privacy and policy bypass, not harmless redundancy.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use a second complete Pi-hole and Unbound pair for high availability instead of a heterogeneous fallback.
Stop conditions
- Do not migrate clients until read-back and end-to-end tests agree.
command
Update gravity and create a deterministic temporary block test
Refresh subscribed lists, inspect errors, add one reserved example name to the exact denylist, and prove that Pi-hole reports the policy match. Remove this fixture after end-to-end client testing.
Why this step matters
A controlled exact deny entry makes filtering acceptance deterministic. Public blocklists change continuously, so testing a random advertising hostname can produce a misleading pass or fail.
What to understand
The .test top-level domain is reserved for testing and should not resolve publicly, avoiding impact on a real service.
pihole -g must complete download, parse, database build, atomic swap, and FTL reload; inspect warnings rather than relying on the final line alone.
pihole query explains which exact, regex, allow, group, or subscribed policy matched and is safer than editing gravity.db directly.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Updates subscribed policy data and adds telemetry.example.test to the default group's exact denylist.
Syntax explained
pihole -g- Rebuilds the gravity database from configured subscription sources and reloads policy.
pihole deny- Adds a validated exact domain rule through the supported CLI.
pihole query- Explains database matches without changing policy.
sudo pihole -g && sudo pihole deny telemetry.example.test && pihole query telemetry.example.test[✓] Building tree [✓] Swapping databases [✓] The old database remains available [✓] Reloading DNS lists Match found in exact deny list telemetry.example.test
Checkpoint: Create an owned filtering fixture
Continue whenGravity rebuild completes without unresolved errors and the reserved test name matches the exact denylist.
Stop whenDatabase swap fails, sources are unexpectedly replaced, or the rule lands in the wrong client group.
If this step fails
The “Create an owned filtering fixture” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: Gravity rebuild completes without unresolved errors and the reserved test name matches the exact denylist.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Database swap fails, sources are unexpectedly replaced, or the rule lands in the wrong client group.
Security notes
- Review list-source ownership and sudden size changes; a compromised list can disrupt or redirect large parts of the network.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Create the exact deny fixture in an isolated canary group when production default-group policy cannot change during testing.
Stop conditions
- Do not use an uncontrolled real business domain as a blocking fixture.
command
Choose query privacy and retention deliberately
Set the approved Pi-hole privacy level after understanding what dashboards and troubleshooting data it removes. The example hides domains and clients while preserving aggregate operational statistics.
Why this step matters
DNS telemetry is useful during incidents but can reveal individual browsing and device activity. Choosing the privacy level before rollout aligns dashboards, retention, access, and troubleshooting expectations.
What to understand
Level 2 hides both domains and clients from statistics, reducing attribution while preserving aggregate health.
A higher level can make historical diagnosis impossible; a lower level creates sensitive operational data that must be protected and expired.
Changing privacy is a governed data-processing change, not merely a cosmetic dashboard preference.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Changes FTL query-statistics visibility and may restart or reconfigure the analytics path; it does not change upstream DNS correctness.
Syntax explained
misc.privacylevel 2- Hides domains and clients while preserving aggregated statistics.
--config ...- Uses Pi-hole v6's supported typed configuration interface and reads the value back.
Values stay on this page and are never sent or saved.
sudo pihole-FTL --config misc.privacylevel {{privacyLevel}} && sudo pihole-FTL --config misc.privacylevel2
Checkpoint: Approve DNS telemetry scope
Continue whenThe configured level matches policy, dashboard access is restricted, and retention plus incident exceptions are documented.
Stop whenOperators expect data the level removes or the organization has not approved storage of the data it retains.
If this step fails
The “Approve DNS telemetry scope” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: The configured level matches policy, dashboard access is restricted, and retention plus incident exceptions are documented.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Operators expect data the level removes or the organization has not approved storage of the data it retains.
Security notes
- Protect query databases and backups as sensitive data even when the dashboard obscures fields.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use level 3 for stronger anonymity or a lower level only with approved retention and access controls.
Stop conditions
- Do not silently lower privacy during troubleshooting.
command
Restrict DNS clients and the administration surface
Allow Pi-hole DNS over UDP and TCP only from {{clientCidr}}, restrict the web interface to {{managementCidr}}, and confirm that Unbound port 5335 has no non-loopback listener or firewall allowance.
Why this step matters
The firewall independently enforces who may query FTL and who may administer it. Socket inspection then verifies that the internal backend did not accidentally cross that boundary.
What to understand
DNS needs both UDP and TCP. Restricting only one transport creates failures that correlate with response size and appear random.
The web interface deserves a narrower management CIDR than ordinary DNS clients because it can change policy and inspect telemetry.
No rule is created for 5335: loopback does not need network permission and any external allowance would be a design defect.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Adds three source-, destination-, interface-, port-, and protocol-specific inbound rules for client DNS and HTTP administration.
Syntax explained
from {{clientCidr}}- Limits client DNS to the explicitly managed source range.
from {{managementCidr}}- Narrows web administration independently of resolver clients.
to {{piHoleAddress}}- Prevents the same rules from exposing another interface address.
Values stay on this page and are never sent or saved.
sudo ufw allow in on {{clientInterface}} proto udp from {{clientCidr}} to {{piHoleAddress}} port 53 comment 'Pi-hole DNS UDP' && sudo ufw allow in on {{clientInterface}} proto tcp from {{clientCidr}} to {{piHoleAddress}} port 53 comment 'Pi-hole DNS TCP' && sudo ufw allow in on {{clientInterface}} proto tcp from {{managementCidr}} to {{piHoleAddress}} port 80 comment 'Pi-hole admin HTTP' && sudo ufw status numbered && sudo ss -lntup | grep -E ':(53|5335)\b'[ 1] 10.24.0.54 53/udp on ens18 ALLOW IN 10.24.0.0/24 # Pi-hole DNS UDP
[ 2] 10.24.0.54 53/tcp on ens18 ALLOW IN 10.24.0.0/24 # Pi-hole DNS TCP
[ 3] 10.24.0.54 80/tcp on ens18 ALLOW IN 10.24.0.0/28 # Pi-hole admin HTTP
udp UNCONN 0 0 10.24.0.54:53 0.0.0.0:* users:(("pihole-FTL",pid=2440,fd=19))
udp UNCONN 0 0 127.0.0.1:5335 0.0.0.0:* users:(("unbound",pid=2281,fd=5))Checkpoint: Accept one client DNS surface
Continue whenFTL owns {{piHoleAddress}}:53, Unbound owns only 127.0.0.1:5335, and rules contain exact source ranges.
Stop whenAny rule says Anywhere, port 5335 is exposed, or the management surface is reachable from ordinary clients.
If this step fails
The “Accept one client DNS surface” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: FTL owns {{piHoleAddress}}:53, Unbound owns only 127.0.0.1:5335, and rules contain exact source ranges.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Any rule says Anywhere, port 5335 is exposed, or the management surface is reachable from ordinary clients.
Security notes
- Keep console access while changing remote firewall state and preserve all unrelated rules.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Integrate equivalent rules into an existing nftables atomic policy after nft -c validation.
Stop conditions
- Never flush the host firewall to troubleshoot Pi-hole.
verification
Prove filtering, recursion, validation, and transport through Pi-hole
From an approved client, query an allowed signed name, the temporary denied name, a deliberately broken DNSSEC chain, and an allowed name over TCP. Compare direct Unbound only when isolating layers.
Why this step matters
The complete chain must demonstrate allowed recursion, deterministic filtering, DNSSEC failure, and TCP from the real client path. Each result exercises a different ownership boundary.
What to understand
The allowed name proves FTL can reach Unbound and public authority. The reserved denied name proves Pi-hole policy acts before forwarding.
The broken signed name proves Unbound still validates after FTL forwarding; a block response for that name would be a different test.
Run commands on an approved client. A server-local query does not prove network firewall or client resolver behavior.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- No persistent change; populates caches and query records according to the configured privacy level.
Syntax explained
@{{piHoleAddress}}- Targets the actual client-facing service instead of bypassing it.
+tcp- Forces the required fallback transport through both layers.
Values stay on this page and are never sent or saved.
dig @{{piHoleAddress}} cloudflare.com A +dnssec +noall +comments +answer; dig @{{piHoleAddress}} telemetry.example.test A +noall +comments +answer; dig @{{piHoleAddress}} dnssec-failed.org A +dnssec +noall +comments; dig @{{piHoleAddress}} www.iana.org A +tcp +noall +answer;; status: NOERROR, id: 18031 cloudflare.com. 300 IN A 104.16.132.229 ;; status: NOERROR, id: 41321 telemetry.example.test. 2 IN A 0.0.0.0 ;; status: SERVFAIL, id: 10912 www.iana.org. 1200 IN A 192.0.33.8
Checkpoint: Accept the complete filtering and validation path
Continue whenAllowed signed data succeeds, fixture is blocked, bogus DNSSEC is SERVFAIL, and TCP returns the same allowed data.
Stop whenAny result is ambiguous, a blocked query reaches Unbound, or direct and layered validation differ.
If this step fails
The “Accept the complete filtering and validation path” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: Allowed signed data succeeds, fixture is blocked, bogus DNSSEC is SERVFAIL, and TCP returns the same allowed data.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Any result is ambiguous, a blocked query reaches Unbound, or direct and layered validation differ.
Security notes
- Use only the reserved fixture and public diagnostic names; do not expose sensitive internal queries in screenshots or tickets.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Automate the four probes from a dedicated canary with bounded metadata retention.
Stop conditions
- Do not start DHCP rollout until all four independent results pass.
instruction
Advertise Pi-hole without a policy-bypass fallback
Move one canary client, then a small DHCP or VPN cohort, and finally the full network. Advertise only controlled DNS servers with equivalent policy and address IPv6 RA or DHCPv6 before declaring success.
Why this step matters
A staged client migration constrains blast radius and exposes resolver selection outside dig: DHCP leases, VPN pushes, static devices, IPv6, browsers, and application-level encrypted DNS.
What to understand
Test representative applications and internal names on the canary, not only a public A record.
Do not advertise a public secondary. Clients may select it during ordinary operation and bypass both filtering and local policy.
Account for lease duration and cache lifetime when interpreting mixed behavior during rollout or rollback.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Changes resolver selection for canary and later network cohorts, affecting every hostname lookup and potentially application availability.
Syntax explained
resolvectl status- Shows per-link DNS servers, routing domains, and current selection on systemd-resolved clients.
resolvectl query- Tests through the client resolver path, including local cache and link selection.
resolvectl status; resolvectl query cloudflare.com; resolvectl query telemetry.example.testLink 2 (ens18)
Current Scopes: DNS
DNS Servers: 10.24.0.54
cloudflare.com: 104.16.132.229 -- link: ens18
telemetry.example.test: 0.0.0.0 -- link: ens18Checkpoint: Promote a canary with no bypass
Continue whenThe client uses controlled DNS on IPv4 and IPv6, applications work, filtering is visible, and old DNS can be restored.
Stop whenAnother resolver appears, required software breaks, or incident telemetry is insufficient.
If this step fails
The “Promote a canary with no bypass” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: The client uses controlled DNS on IPv4 and IPv6, applications work, filtering is visible, and old DNS can be restored.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Another resolver appears, required software breaks, or incident telemetry is insufficient.
Security notes
- Manage application DoH explicitly rather than intercepting encrypted traffic without a documented endpoint policy.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use endpoint-managed DNS settings when DHCP cannot govern roaming or untrusted networks.
Stop conditions
- Pause between cohorts and reverse the last cohort on the first unexplained resolution failure.
command
Back up policy and establish layer-specific operations
Create a protected configuration archive containing Pi-hole configuration, gravity database, and Unbound configuration plus anchor state. Establish probes that can distinguish client-to-Pi-hole failure from Pi-hole-to-Unbound or Unbound-to-authority failure.
Why this step matters
The appliance contains policy, configuration, privacy settings, and trust state that cannot be reconstructed safely from memory during an outage. Layer-specific probes prevent a generic 'DNS down' alert from obscuring the failing component.
What to understand
The archive includes pihole.toml, gravity.db, Unbound configuration, and root.key, but a backup is useful only after an isolated restoration drill.
Protect the archive because gravity policy and configuration can reveal internal structure; query databases are deliberately excluded from this minimal set.
Monitor FTL port 53, Unbound 5335, signed success, bogus failure, fixture blocking, TCP, denied sources, time, and list-update status.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Creates a root-readable backup directory and dated compressed archive; reads live configuration and databases without stopping services.
Syntax explained
install -m 0700- Creates a backup directory accessible only to root by default.
tar --list- Verifies expected archive members without extracting them over live state.
sudo install -d -m 0700 /var/backups/oneliners-dns && sudo tar --create --gzip --file /var/backups/oneliners-dns/pihole-unbound-$(date +%F).tar.gz /etc/pihole/pihole.toml /etc/pihole/gravity.db /etc/unbound/unbound.conf.d/pi-hole.conf /var/lib/unbound/root.key && sudo tar --list --file /var/backups/oneliners-dns/pihole-unbound-$(date +%F).tar.gzetc/pihole/pihole.toml etc/pihole/gravity.db etc/unbound/unbound.conf.d/pi-hole.conf var/lib/unbound/root.key
Checkpoint: Make both DNS layers recoverable and observable
Continue whenArchive members are complete, protected off-host copy and retention exist, and semantic probes have owners.
Stop whenThe archive contains secrets with broad permissions, cannot be restored, or monitoring checks only process uptime.
If this step fails
The “Make both DNS layers recoverable and observable” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: Archive members are complete, protected off-host copy and retention exist, and semantic probes have owners.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. The archive contains secrets with broad permissions, cannot be restored, or monitoring checks only process uptime.
Security notes
- Encrypt off-host backups and never include unrestricted long-term query history without explicit approval.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Use Pi-hole's supported Teleporter export plus configuration management and separate protected Unbound state backups.
Stop conditions
- Do not call a backup complete until a non-production restoration reproduces effective settings.
command
Remove the temporary deny fixture and close the change
Remove only the reserved test name, confirm it is absent from policy, repeat one allowed and one production-relevant blocked-domain check, and retain the acceptance evidence with the change record.
Why this step matters
Acceptance policy should not outlive its documented purpose. Removing the reserved fixture proves operators can reverse a narrow rule and avoids future confusion when someone investigates an unexplained block.
What to understand
The remove subcommand changes only the named exact deny entry; pihole query verifies no other rule still matches it.
The post-removal DNS result may be NXDOMAIN because .test is reserved and absent publicly. The important difference is that Pi-hole no longer reports a deny policy match.
Retain the before-and-after evidence, effective upstream, listener list, privacy level, and client acceptance output.
Keep the command output with the change record. DNS is distributed and cached, so an immediate local success is only one layer of evidence; later checkpoints deliberately test authoritative behavior, delegation, validation, transport, and client-visible results separately.
Run the exact read-only checkpoint before moving on. If its evidence differs from the example, diagnose that layer instead of stacking another configuration change, restarting repeatedly, or weakening a security control to make a query appear successful.
System changes
- Removes telemetry.example.test from the exact denylist and reloads FTL policy.
Syntax explained
pihole deny remove NAME- Uses the supported CLI to delete one exact deny entry.
pihole query NAME- Confirms the test name no longer matches any stored policy.
Values stay on this page and are never sent or saved.
sudo pihole deny remove telemetry.example.test && pihole query telemetry.example.test; dig @{{piHoleAddress}} telemetry.example.test A +noall +comments +answer[✓] Removed telemetry.example.test from deny list [✓] Reloading DNS lists No results found for telemetry.example.test ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48610
Checkpoint: Close without test residue
Continue whenThe fixture has no policy match, production policy remains intact, and all acceptance evidence is attached.
Stop whenAnother rule still matches, unrelated entries changed, or the final client path differs from the accepted topology.
If this step fails
The “Close without test residue” checkpoint does not produce the expected evidence.
Likely causeThe preceding change is incomplete, the observed system differs from the documented assumptions, or the checkpoint reached a different DNS component than intended. Expected evidence: The fixture has no policy match, production policy remains intact, and all acceptance evidence is attached.
Compare the observed state with the documented checkpoint evidence before making another change.Review the immediately preceding command or configuration diff, service status, listener ownership, journal timestamps, and the exact server address queried.
ResolutionStop at this layer and correct the discrepancy before continuing. Another rule still matches, unrelated entries changed, or the final client path differs from the accepted topology.
Security notes
- Review the exact rule before deletion so cleanup cannot remove a legitimate production control with a similar name.
- Do not paste private names, client addresses, keys, complete query logs, or registrar account details into public support channels. Redact evidence while retaining timestamps, response codes, flags, serials, key tags, and server identities needed for diagnosis.
Alternatives
- Retain a dedicated documented synthetic probe in an isolated test group when continuous block monitoring is required.
Stop conditions
- Do not close the change with undocumented emergency upstreams, privacy exceptions, or fixture rules.
Finish line
Verification checklist
sudo ss -lntup | grep -E ':(53|5335)\b'Pi-hole FTL owns {{piHoleAddress}} TCP/UDP 53; Unbound owns only 127.0.0.1 TCP/UDP 5335.sudo pihole-FTL --config dns.upstreams; sudo pihole-FTL --config dns.dnssecThe only upstream is 127.0.0.1#5335 and Pi-hole DNSSEC is false because Unbound validates.dig @127.0.0.1 -p 5335 cloudflare.com A +dnssec +noall +comments; dig @127.0.0.1 -p 5335 dnssec-failed.org A +dnssec +noall +commentsThe signed name succeeds with AD and the deliberately broken chain returns SERVFAIL.sudo pihole deny {{testBlockedName}}; dig @{{piHoleAddress}} {{testBlockedName}} A +noall +comments +answer; sudo pihole deny remove {{testBlockedName}}The temporary exact deny entry produces the configured blocked answer through the client-facing address and is removed afterward.dig @{{piHoleAddress}} www.iana.org A +noall +comments +answer; dig @{{piHoleAddress}} www.iana.org A +tcp +noall +answerApproved clients resolve over UDP and TCP; a separately tested unapproved source receives no recursive answer.Recovery guidance
Common problems and safe checks
Pi-hole returns SERVFAIL for every allowed public name, but blocked names still produce a local response.
Likely causeFTL policy works but the 127.0.0.1#5335 upstream is unavailable, misconfigured, or failing validation.
Query dig @127.0.0.1 -p 5335 cloudflare.com A +dnssec directly.Run unbound-checkconf, systemctl status unbound, and inspect port 5335 ownership.Check time, root.key ownership, and journalctl -u unbound.
ResolutionRepair Unbound configuration, time, anchor, or transport; do not add an external Pi-hole upstream until the intended privacy change is explicitly approved.
Direct Unbound queries work, but Pi-hole reports no upstream servers or still uses an old resolver.
Likely causeThe v6 dns.upstreams value was not persisted, an environment override makes it read-only, or FTL did not reload the effective configuration.
Run sudo pihole-FTL --config dns.upstreams.Inspect /etc/pihole/pihole.toml and service environment for FTLCONF_dns_upstreams.Review FTL status and logs without editing the database.
ResolutionRemove or change the authoritative configuration source, set the array to only 127.0.0.1#5335, and repeat end-to-end tests.
Some clients bypass filtering while others work.
Likely causeA public secondary DNS, IPv6 RA/DHCPv6 resolver, application DoH, VPN, static setting, or cached lease provides another path.
Inspect resolver configuration on an affected client over both IPv4 and IPv6.Compare Pi-hole query visibility with a packet capture approved for that client.Review DHCP, RA, VPN, browser, and device management DNS settings.
ResolutionRemove unintended resolver advertisements or enforce managed endpoint policy; do not claim network-wide coverage while a known bypass remains.
Clients can resolve over UDP but selected names fail or TCP tests time out.
Likely causeTCP port 53 is blocked on the client-facing firewall or outbound authority path, often exposed by large DNSSEC responses.
Run dig +tcp at Pi-hole and directly at Unbound port 5335.Inspect UFW or nftables counters for both client and outbound paths.Check path MTU and whether a network device intercepts DNS.
ResolutionPermit stateful TCP DNS on the exact required paths, correct MTU or interception, and repeat signed-answer tests.
A domain remains blocked after being allowlisted.
Likely causeA regex or CNAME target still matches, the client belongs to another group, policy reload failed, or an answer remains cached.
Run pihole query for the requested name and relevant CNAME targets.Inspect group assignments and exact versus regex policy in the admin interface.Compare a direct Unbound answer with Pi-hole and client answers.
ResolutionCorrect the narrowest policy or group assignment, reload through supported commands, and wait or clear only the relevant cache when necessary.
The dashboard is empty after privacy hardening, making an incident difficult to investigate.
Likely causeThe selected privacy level intentionally hides domains and clients; missing data is policy behavior, not a resolver failure.
Read sudo pihole-FTL --config misc.privacylevel.Check aggregate service, cache, and upstream health without reducing privacy.Review the approved incident exception and retention policy.
ResolutionUse aggregate and layer-specific protocol evidence first; lower privacy only through an authorized time-bounded incident change and restore it afterward.
Reference
Frequently asked questions
Should DNSSEC be enabled in both Pi-hole and Unbound?
Not in this topology. Unbound is the validating recursive owner and Pi-hole forwards to it. A single owner makes failures and evidence clearer while still rejecting bogus signed data.
Why use 127.0.0.1#5335?
It gives Unbound a non-conflicting internal socket that only local Pi-hole can reach. The # syntax is Pi-hole's host-and-port upstream format; clients continue to use standard port 53.
Does LOCAL listening mode replace the firewall?
No. LOCAL is a useful application default based on connected subnets. The firewall independently narrows sources, destination address, interface, and the administration surface.
Will Pi-hole block every advertisement or tracker?
No. DNS filtering cannot separate content that shares an allowed hostname and can be bypassed by alternate DNS or direct connections. Treat it as one policy layer with expected false positives and false negatives.
Can clients use Unbound directly as a backup?
No. That bypasses Pi-hole filtering and requires exposing port 5335. If availability needs a second DNS server, deploy another complete controlled pair and test equivalent policy.
Recovery
Rollback
Restore client DNS before disabling the new path. Then reverse Pi-hole upstream, firewall, or service changes one layer at a time while preserving logs and configuration evidence.
- Restore the previous DHCP, VPN, IPv4, and IPv6 resolver advertisements for the affected cohort and renew one canary client.
- If only Unbound failed, temporarily restore the reviewed previous Pi-hole upstream through pihole-FTL --config dns.upstreams and verify it; do not leave an undocumented emergency resolver.
- Restore /etc/pihole/pihole.toml and gravity.db from the protected archive using the Pi-hole-supported recovery process, then restart FTL and verify listeners.
- Restore the previous Unbound configuration, run unbound-checkconf, and restart only after static validation passes.
- Remove firewall rules by inspecting their current exact identities; never flush unrelated policy. Confirm 5335 was never exposed externally.
- Remove the temporary telemetry.example.test deny entry if an earlier step stopped before cleanup.
Evidence