Unbound Recursive Resolver: An In-Depth Guide
Every time you open a website, send an email or launch an app, a DNS lookup happens first. Something has to turn a name like example.com into an IP address, and that something is a resolver. Unbound is one of the best resolvers you can run yourself: small, fast, security-focused and free.
This guide covers what Unbound is, where it came from, how recursive resolution actually works, why people choose to run it, and how to install, configure, secure, tune, monitor and troubleshoot it. It also explains how Unbound fits next to Pi-hole and other tools, and where it is the wrong choice.
What is Unbound?
Unbound is an open-source validating, recursive, caching DNS resolver. Each of those words matters:
- Recursive. Unbound doesn't just forward your question to someone else. It does the whole job itself, starting at the DNS root and following referrals down to the server that is actually authoritative for the name you asked about.
- Caching. Answers are stored for as long as their TTL allows, so repeat lookups are answered from memory in well under a millisecond instead of travelling across the internet.
- Validating. Unbound checks DNSSEC signatures, so it can detect forged or tampered answers for domains that are signed, and refuse to hand them to your devices.
- A resolver, not an authoritative server. Unbound answers questions on behalf of clients. It does not host the official records for your domain. That is a different job, done by software such as NSD, Knot DNS, PowerDNS or BIND.
Unbound is written in C, released under the permissive BSD licence, and runs on Linux, the BSDs, macOS, Windows and most things in between. It is built for clients, home networks, ISPs and enterprises alike.
Where Unbound comes from
Unbound was created by NLnet Labs, a Dutch not-for-profit that has built a lot of core internet infrastructure software, including the NSD authoritative server. The first release arrived in 2008 as a collaboration between NLnet Labs, Verisign, Nominet and Kirei. The goal was a modern resolver with security and performance as design priorities rather than features bolted on later.
That history explains its reputation. Unbound has a compact codebase, a conservative approach to security, and a long record of careful maintenance. It is also the resolver many operating systems and appliances chose as their default:
- It replaced BIND as the base-system resolver in FreeBSD and is the default resolver in OpenBSD.
- Firewall distributions such as pfSense and OPNsense ship it as their DNS resolver.
- Many Linux distributions package it, and a large number of ISPs and enterprises run it at scale.
How recursive resolution works
To understand why Unbound exists, it helps to follow one lookup from start to finish. Say your laptop asks for www.example.com and nothing is cached.
- Your device asks a stub resolver, the small piece of code built into the operating system. The stub doesn't do the hard work. It just sends the question to the resolver it has been configured with. That is where Unbound sits.
- Unbound checks its cache. If it has a fresh answer, it replies immediately and the story ends here.
- Unbound asks a root server. It starts from a built-in list of root server addresses, the root hints. The root servers don't know the answer, but they know who runs
.com, so they send back a referral to the.comservers. - Unbound asks a
.comserver. That server doesn't know the address either, but it knows which name servers are responsible forexample.com, and refers Unbound to them. - Unbound asks the authoritative server for
example.com. This one holds the real record and returns the IP address forwww.example.com. - Unbound validates and caches. If the zone is signed, Unbound checks the DNSSEC signatures up the chain. Then it stores the answer and returns it to your device.
Every step is also cached. After the first lookup, Unbound already knows where the .com servers are and who runs example.com, so the next lookup under that domain skips most of the journey. In a busy network, most queries are answered straight from the cache.
Recursive resolving versus forwarding
This distinction is the heart of why many people install Unbound.
A forwarding resolver hands your questions to a bigger upstream resolver, such as your ISP's or a public one, and relays the answer. It is simple and often fast, but that upstream sees every domain you look up, and you have to trust what it tells you.
A recursive resolver like Unbound goes to the authoritative servers directly. No single company sees your complete lookup history, because each authoritative server only sees the questions about the domains it is responsible for. You trade a little first-lookup speed for independence.
Unbound can do either, or both at once. You can recurse by default and forward specific zones, such as a corporate domain, to specific servers.
Why run your own recursive resolver?
There are practical reasons, and they are worth weighing honestly.
- Less dependence on third parties. You are not relying on an ISP resolver that may be slow, unreliable or filtered, or on a large public provider that logs queries.
- Better privacy against concentration. Your queries are spread across many authoritative servers instead of piling up with one operator. Be clear about the limit here: the authoritative servers still see your resolver's IP address, and most of that traffic is not encrypted on the way. It reduces centralisation, it does not make you invisible.
- Real DNSSEC validation under your control. You decide that validation happens, rather than hoping an upstream does it, and your devices only receive answers that passed the check.
- Speed for repeat lookups. A resolver on your own network answers cached queries in a fraction of a millisecond, which makes pages and apps feel snappier.
- Local naming. You can give your own devices names, create split-horizon DNS, and override or block specific domains.
- Learning and control. For home labs and small networks, running the resolver yourself is a good way to understand how DNS really behaves.
The honest downsides: the first lookup of a cold name is slower than asking a big cached public resolver, you take on the job of keeping it patched and running, and if your connection filters or breaks direct DNS to authoritative servers, recursion can fail where forwarding would work.
Key features of Unbound
- DNSSEC validation with automatic trust-anchor handling for the root.
- Query name minimisation (RFC 7816 and RFC 9156). Unbound sends only as much of the name as each server needs, so the root learns about
.comand not the fullwww.example.com. Modern versions enable this by default. - Aggressive use of the DNSSEC cache (RFC 8198). Cached proofs that a name doesn't exist can answer other queries without going upstream, which reduces traffic and helps against random-subdomain attacks.
- Prefetching. Popular records are refreshed shortly before they expire, so users rarely wait on a cache miss for the sites they use most.
- Serving stale data (RFC 8767). If upstream servers are unreachable, Unbound can briefly answer from expired cache entries instead of failing.
- Encrypted DNS support. Unbound can talk to upstream servers over DNS-over-TLS, and can serve its own clients over DoT and DoH when you configure certificates. DoH needs a build with HTTP/2 support. Newer releases have been adding DNS-over-QUIC, so check the release notes of the version you run.
- Local zones and local data for custom records, blocklists and split DNS.
- Stub and forward zones to route chosen domains to chosen servers.
- Access control to decide which clients may query it.
- Rate limiting and hardening options to resist abuse and spoofing.
- A control channel (
unbound-control) for stats, cache inspection and live changes without restarts. - Modules and extensions, including a Python module, a cache database backend such as Redis, response IP tagging and dnstap logging.
- Multithreading that scales across CPU cores.
Installing Unbound
Package names are simple on most systems.
On Debian and Ubuntu:
sudo apt update
sudo apt install unbound
On Fedora, RHEL and derivatives:
sudo dnf install unbound
On Alpine Linux:
apk add unbound
On macOS with Homebrew:
brew install unbound
On FreeBSD it is already in the base system, and on OpenBSD it is the default resolver. Docker images exist too, though for a resolver that other devices depend on, a plain package install is usually simpler to keep reliable.
Two helper tools usually come with the package:
unbound-checkconfvalidates your configuration file before you restart anything. Run it every time you edit.unbound-anchorfetches and updates the DNSSEC root trust anchor. Many packages run it for you and set up the file automatically.
One common obstacle on Linux: another service may already be using port 53. On Ubuntu, systemd-resolved runs a local stub listener there. Either set Unbound to listen on a different address or port, or disable that stub listener. Check with sudo ss -lntup | grep ':53'.
The configuration file
The main file is usually /etc/unbound/unbound.conf, and distributions often include extra files from a directory such as /etc/unbound/unbound.conf.d/. Configuration is organised in blocks, and the most important one is server:. A good configuration for a home or small-office resolver looks like this:
server:
# Listening
interface: 0.0.0.0
port: 53
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes
# Who may use this resolver
access-control: 127.0.0.0/8 allow
access-control: 192.168.0.0/16 allow
access-control: 10.0.0.0/8 allow
# Privacy of the server itself
hide-identity: yes
hide-version: yes
qname-minimisation: yes
# Hardening
harden-glue: yes
harden-dnssec-stripped: yes
harden-referral-path: yes
use-caps-for-id: no
unwanted-reply-threshold: 10000000
# Performance
num-threads: 2
msg-cache-size: 64m
rrset-cache-size: 128m
prefetch: yes
prefetch-key: yes
edns-buffer-size: 1232
# Resilience
serve-expired: yes
serve-expired-ttl: 86400
serve-expired-client-timeout: 1800
Treat this as a starting point and run unbound-checkconf before applying it. Defaults differ slightly between versions and distributions, so confirm option names against the unbound.conf manual page for what you have installed.
What the important options do
interfaceandport. Where Unbound listens.127.0.0.1keeps it private to the machine,0.0.0.0listens on all IPv4 addresses. When Unbound sits behind something else, a non-standard port such as 5335 is common.access-control. The most important security line in the file. Unbound refuses queries from addresses you have not allowed. Without it, a resolver that listens on a public address can become an open resolver.hide-identityandhide-version. Stop the server from revealing its name and version to anyone who asks.qname-minimisation. Sends the minimum necessary part of each name to each server. Leave it on unless a broken authoritative server forces you to relax it.harden-glue,harden-dnssec-stripped,harden-referral-path. Defensive checks against poisoned or downgraded responses.harden-dnssec-strippedis the one that stops an attacker from simply removing signatures.use-caps-for-id. Randomises letter case in outgoing queries as extra spoofing protection (the "0x20" trick). It is a security feature, but some authoritative servers mishandle it, so many home setups leave it off. Turn it on if you want the extra protection and are willing to investigate rare failures.edns-buffer-size: 1232. Keeps UDP responses small enough to avoid IP fragmentation, which was a recurring source of failures and an attack surface. This value follows the DNS Flag Day 2020 recommendation.prefetch. Refreshes popular entries just before they expire. It increases upstream traffic slightly and reduces visible latency.serve-expired. Lets Unbound answer from stale cache briefly when upstream resolution is failing. It makes outages feel milder.
DNSSEC with Unbound
DNSSEC adds digital signatures to DNS data so a resolver can prove an answer is genuine and unmodified. Unbound validates by following a chain of trust from a trust anchor, the root zone's public key, down through each delegation.
Setup is usually automatic. A line such as the following points Unbound at a root trust anchor file that unbound-anchor maintains:
server:
auto-trust-anchor-file: "/var/lib/unbound/root.key"
Distribution packages often configure this for you, so check before adding it twice.
Understand how results look. A validated answer carries the AD (Authenticated Data) flag. An answer that fails validation is returned as SERVFAIL, not as a warning, because the whole point is to refuse forged data. You can check with dig:
dig +dnssec example.com @127.0.0.1
dig sigfail.verteiltesysteme.net @127.0.0.1
dig sigok.verteiltesysteme.net @127.0.0.1
The sigfail test domain is deliberately broken, so a validating resolver should return SERVFAIL for it, while sigok should resolve with the ad flag set.
Two things trip people up. First, DNSSEC signatures have validity periods, so a machine with a badly wrong clock can reject perfectly good data. Keep time synchronised. Second, a domain whose owner has misconfigured DNSSEC will fail to resolve through a validating resolver even though it works elsewhere. That is DNSSEC working as designed, but it can look like Unbound is at fault.
Encrypted DNS with Unbound
There are two separate directions to think about.
Encrypting traffic to your upstream (forwarding over TLS)
If you choose to forward rather than recurse, you can at least encrypt the hop to the upstream resolver:
server:
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 9.9.9.9@853#dns.quad9.net
The part after # is the name Unbound checks the server's certificate against. Forwarding like this protects the path from eavesdroppers, but the upstream provider still sees your queries, so it is a different privacy trade-off from true recursion.
Serving DoT and DoH to your own devices
Unbound can also be the encrypted endpoint that your phone or laptop connects to. For DoT you provide a certificate and key and open the TLS port:
server:
interface: 0.0.0.0@853
tls-service-key: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
tls-service-pem: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
tls-port: 853
For DoH, Unbound listens on an HTTPS port with the same certificate. It requires a build with HTTP/2 support, so check that your package has it:
server:
interface: 0.0.0.0@443
https-port: 443
tls-service-key: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
tls-service-pem: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
Three practical warnings. You need a real certificate from a public certificate authority, because phones and browsers reject self-signed ones. You need a hostname that points at your server and ports reachable from where your devices roam. And you must keep access-control tight, because an encrypted endpoint open to the whole internet is still an open resolver, and attackers can use it for amplification attacks. Many people put a dedicated front end such as dnsdist or nginx in front of Unbound to handle TLS and authentication, and let Unbound do only what it is best at.
Local zones, local data and split DNS
Unbound can answer for names that exist only on your network.
server:
local-zone: "home.lan." static
local-data: "nas.home.lan. IN A 192.168.1.20"
local-data: "printer.home.lan. IN A 192.168.1.30"
local-data-ptr: "192.168.1.20 nas.home.lan"
The static zone type answers authoritatively from your data and returns NXDOMAIN for anything else in that zone. Other zone types change the behaviour: transparent serves your data but lets other names resolve normally, and always_nxdomain or always_refuse are used for blocking.
Blocking a single domain is as simple as:
server:
local-zone: "ads.example.net" always_nxdomain
That works for small lists, but it is not a good way to manage tens of thousands of domains. For large blocklists, a dedicated tool such as Pi-hole in front is far easier to maintain.
Split-horizon DNS is the same idea applied to one name that should resolve differently inside and outside your network, for example returning a private address for app.example.com to internal clients. Local data makes that straightforward.
Stub zones, forward zones and private domains
Real networks often have domains that public DNS can't answer. Unbound handles them in two ways.
forward-zonesends queries for a domain to servers you specify and relies on them to do the work. Use it for an internal corporate domain served by an internal DNS server.stub-zonepoints Unbound at the authoritative servers for a zone, and Unbound still iterates from there. It suits cases where you know exactly which servers hold a zone's data.
forward-zone:
name: "corp.example.com."
forward-addr: 10.0.0.53
Two related settings matter for security. private-address tells Unbound to discard public answers that contain private IP ranges, which blocks a DNS rebinding trick where a malicious public domain points at your internal devices. private-domain exempts specific domains from that rule so your own internal names still work.
Running Unbound with Pi-hole
Pi-hole is a network-wide ad and tracker blocker that works by answering blocked domains with a null response. Pi-hole is not a recursive resolver itself. It needs an upstream to ask for everything that isn't blocked, and that is where Unbound fits neatly.
The usual arrangement keeps each tool in its own lane:
- Clients send DNS queries to Pi-hole.
- Pi-hole filters against its blocklists and logs the query.
- Anything allowed is forwarded to Unbound on the same machine.
- Unbound recurses, validates and caches, then returns the answer.
Because both run on one box, Unbound listens only on localhost with an unusual port so it doesn't collide with Pi-hole on port 53:
server:
verbosity: 0
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
edns-buffer-size: 1232
prefetch: yes
num-threads: 1
private-address: 192.168.0.0/16
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
Then, in Pi-hole's DNS settings, remove the public upstream servers and add 127.0.0.1#5335 as a custom upstream. Restart both services and test:
dig example.com @127.0.0.1 -p 5335
A few notes on this pairing. Pi-hole's logs will show every client, while Unbound only sees Pi-hole, which is normal. Both layers can do DNSSEC, and running validation in Unbound is enough. And the first visit to a never-seen domain will be a little slower than with a big public upstream, then fast thereafter, because Unbound's cache warms up.
Performance tuning
Unbound is fast by default. For busy networks or small hardware, these are the knobs that matter.
num-threads. Match it to the number of CPU cores you want Unbound to use. For a household, one or two is plenty. Cache slab settings such asmsg-cache-slabsandrrset-cache-slabsshould be a power of two close to the thread count.- Cache sizes.
msg-cache-sizestores whole answers andrrset-cache-sizestores individual record sets. A common rule of thumb is to make the rrset cache about twice the message cache. On a Raspberry Pi, tens of megabytes is generous. On a busy server, hundreds is reasonable. Larger caches only help if you have enough distinct queries to fill them. prefetchandprefetch-key. Cheap and useful for latency.cache-min-ttl. Forces a minimum lifetime on cached records. It can reduce upstream traffic, but pushing it high risks serving outdated addresses, so keep it modest, such as a few minutes.so-reuseportand socket buffers. On high-volume servers, enablingso-reuseportand raisingso-rcvbufandso-sndbufhelps spread load and avoids dropped packets.infra-cachesettings. Unbound remembers how fast and reliable each upstream server is, and prefers the good ones. You rarely need to touch this, but it is the reason Unbound gets quicker over time.
Measure before and after. unbound-control stats_noreset reports cache hit rate and latency distribution, and a hit rate in the 70 to 90 percent range is normal for a busy household.
Security and hardening
A resolver is a piece of internet-facing infrastructure the moment it listens beyond localhost, so treat it that way.
- Never run an open resolver. Restrict
access-controlto your own networks. Open resolvers are routinely abused for DDoS amplification and will be found by scanners within hours. - Prefer binding to specific interfaces instead of everything, and use a firewall to limit who can reach port 53 as a second layer.
- Rate limit.
ip-ratelimitlimits queries per client address, andratelimitlimits queries Unbound sends to a single zone, which blunts random-subdomain abuse. - Keep it updated. Resolver bugs get fixed regularly, and a long-lived server is a juicy target. Subscribing to NLnet Labs' announcements is worthwhile.
- Run unprivileged and chrooted if you can. Unbound drops privileges by default and many packages already enable a chroot. Keep that behaviour.
- Protect the control channel.
unbound-controllistens on localhost with certificates by default. Do not expose it to the network. - Keep the DNSSEC hardening options on, as shown in the configuration above.
Day-to-day management with unbound-control
The control utility lets you work with a live server. First enable it in unbound.conf:
remote-control:
control-enable: yes
control-interface: 127.0.0.1
Then set up its keys if your package hasn't done so, with unbound-control-setup. Useful commands include:
unbound-control statusshows whether the server is running and its basic details.unbound-control stats_noresetprints counters such as total queries, cache hits and misses, and request times.unbound-control flush example.comremoves one name from the cache.unbound-control flush_zone example.comremoves everything under a domain.unbound-control dump_cache > cache.txtandload_cache < cache.txtsave and restore the cache across restarts.unbound-control list_forwardsandlist_local_datashow what is configured.unbound-control reloadreloads the configuration without dropping cache.
For graphs, a Prometheus exporter for Unbound is available and pairs well with Grafana, which is useful for spotting query spikes and falling hit rates.
Troubleshooting
Most Unbound problems fall into a small number of patterns.
- Won't start. Run
unbound-checkconffirst. Typical culprits are typos, a port already in use, or file permissions on certificate and key files. - SERVFAIL for one domain only. Test with
dig example.com @127.0.0.1and then again with+cd, which disables DNSSEC checking. If it works only with+cd, the domain has a DNSSEC problem on the owner's side or your clock is wrong. - SERVFAIL for everything. Usually the machine can't reach root or authoritative servers, because a firewall blocks outbound UDP and TCP on port 53, an ISP interferes with DNS, or the root trust anchor file is missing or unreadable.
- Very slow first lookups. Normal for a cold cache, but sustained slowness points to blocked TCP fallback, large responses being dropped (see
edns-buffer-size), or IPv6 configured but not actually working. If IPv6 is broken on your network, setdo-ip6: no. - Some sites fail only through Unbound. Possibly a broken authoritative server that mishandles case randomisation or query minimisation. Temporarily test with
use-caps-for-id: noorqname-minimisation: noto identify the cause, then decide whether to keep the relaxed setting for specific domains. - Stale or wrong answers. Flush the name with
unbound-control flush. If you setcache-min-ttlhigh, lower it. - Clients get "refused". The client's address isn't covered by
access-control. - Local names don't resolve. Check the zone type, the trailing dots in names, and that your clients are using Unbound rather than a router that hands out its own DNS.
For deeper debugging, raise verbosity from 0 up to 2 or 3 temporarily, or use log-queries: yes while testing. Turn both off afterwards, since they produce a lot of output and logging queries has privacy implications.
Unbound versus other resolvers
- Pi-hole is a filter and dashboard, not a recursive resolver. It complements Unbound rather than competing with it.
- dnsmasq is a lightweight forwarder with DHCP, common on routers. It is simple and good enough for small setups, but it forwards rather than recursing and has more limited DNSSEC and hardening features.
- BIND is the long-standing do-everything DNS server. It can recurse, but it is large and complex, and it is usually chosen when you also need it as an authoritative server.
- Knot Resolver is another modern, validating, scriptable recursive resolver, with a different design built around Lua configuration. It is a strong alternative, and the choice between it and Unbound is often a matter of taste and ecosystem.
- PowerDNS Recursor is a high-performance recursor popular with operators who already use the PowerDNS family.
- CoreDNS is common in Kubernetes. It is mostly a plugin-driven forwarder and service-discovery tool, not a general recursive resolver.
If you want a straightforward, well-documented, low-maintenance recursive resolver with excellent defaults, Unbound is hard to beat.
What Unbound will not do
Honest limits matter, because the most common disappointment comes from expecting more privacy than DNS can deliver.
- It does not hide your browsing from your ISP. Your provider still sees the IP addresses you connect to and often the site name in the TLS handshake.
- It does not encrypt recursion. Traffic from Unbound to most authoritative servers is plain DNS. Encrypted transport between recursive resolvers and authoritative servers exists but is not widely deployed yet.
- It is not a VPN. It handles name lookups only.
- It does not block ads by itself. You can build blocklists with local zones, but a dedicated filter is the better tool.
- It will not fix a bad network. If your connection mangles DNS, recursion can fail where forwarding to a trusted encrypted upstream would succeed.
Frequently asked questions
Is Unbound free? Yes. It is open source under the BSD licence, and there is no paid edition required to use it.
Is Unbound a DNS server? It is a DNS resolver. A DNS server in the authoritative sense is something else, and Unbound deliberately does not do that job, so it stays small and focused.
Do I need Unbound if I use Pi-hole? No. Pi-hole works with any upstream. Unbound is an option that gives you recursion and local validation instead of depending on an outside upstream.
Does Unbound make browsing faster? Repeat lookups are faster because they come from a local cache. A cold lookup can be slower than a large public resolver that has already cached the name. With prefetching and a warm cache, the day-to-day experience is usually as fast or faster.
Is it safe to expose Unbound to the internet? Only with strict access control, rate limiting, and a clear reason. For almost every home user, keep it on the local network and reach it remotely through a VPN or a properly secured encrypted endpoint.
Can Unbound run on a Raspberry Pi? Yes, easily. It uses very little memory and CPU, which is why it is so popular on small boards.
Does it support IPv6? Yes, for clients and for talking to upstream servers. If your IPv6 connectivity is unreliable, disable it with do-ip6: no rather than living with timeouts.
How often should I update it? Whenever your distribution ships security updates, and at least every few months. Check the release notes for new features like improved encrypted DNS support.
Why did DNSSEC validation break a site that works elsewhere? Because the domain's DNSSEC records are wrong or expired, and your resolver is correctly refusing them. Test with dig +cd to confirm, and contact the domain owner if it is their mistake.
Summary
- Unbound is a free, open-source, validating, recursive, caching DNS resolver from NLnet Labs, trusted in operating systems, firewalls and large networks.
- Recursion means Unbound resolves names itself from the root down, so no single upstream sees all your lookups. That is better for independence, though it does not make you anonymous.
- DNSSEC validation protects you from forged answers for signed domains. Failures show up as SERVFAIL, and clock accuracy matters.
- A solid configuration has tight access control, the hardening options on, sensible cache sizes, prefetching, and serving stale data as a safety net.
- Encrypted DNS works in both directions: forwarding upstream over TLS, and serving DoT or DoH to your own devices. Public endpoints must never be open resolvers.
- With Pi-hole, Unbound becomes the upstream that does the real resolving, while Pi-hole handles blocking and visibility.
- Run it well by keeping it patched, watching its statistics, and testing with
digandunbound-controlwhen something looks wrong.
Want the benefits without the build?
Setting up Unbound properly is very doable, and this guide gives you everything needed to do it. But a resolver people depend on is also a small ongoing job: certificates to renew, ports to expose safely, updates to apply, and a phone that stops loading pages whenever something quietly breaks.
If you would rather just have it working, NxValue Stack provisions a private Pi-hole/AdGuard Home + Unbound node for you, with DoH and DoT endpoints already generated. You copy a hostname into your device settings instead of building the stack, and it works anywhere your phone has a connection, not only on your home network.