There is a point in a homelab where IP addresses stop feeling empowering and start feeling primitive.
At the beginning, remembering that one VM is 10.32.32.40 and another is 10.32.32.41 feels manageable.
Later, it feels like unnecessary friction.
That is usually the moment local DNS starts becoming worth the effort.
And for me, the cleanest mental model was finally this:
- Pi-hole answers the clients;
- Pi-hole owns the local names and filtering logic;
- Unbound handles recursive upstream lookups.
Once those roles stopped blending together in my head, the whole setup felt much calmer.
- Why Pi-hole and Unbound solve different parts of the DNS story
- How VMs actually start using Pi-hole for DNS
- Where local VM names should live
- How Unbound fits in as the recursive upstream
- How I verify the whole flow without guessing
Pi-hole and Unbound are easier to trust once their jobs are separated clearly
Pi-hole’s official Unbound guide explains the normal DNS flow very well. In a standard Pi-hole setup, clients ask Pi-hole, Pi-hole checks cache and blocking rules, and then forwards unresolved public queries to configured upstream DNS servers. After the Unbound setup in Pi-hole’s own guide, that flow changes: Pi-hole still receives and filters the client request, but it delegates unresolved public queries to a local recursive resolver instead.
That is a really helpful distinction.
Pi-hole is not magically becoming “all DNS.”
It is still the client-facing layer.
Unbound is also not replacing Pi-hole’s local role.
It is the recursive resolver behind it.
I think a lot of homelab DNS confusion comes from letting those roles blur together. Once they are cleanly separated, the architecture becomes much easier to reason about:
- VM asks Pi-hole for a name;
- Pi-hole answers locally if it already knows the name or if the local record exists;
- Pi-hole forwards only the unresolved public question to Unbound;
- Unbound performs recursion and returns the answer;
- Pi-hole replies to the VM and caches what it should.
That is a much better mental model than “both are somehow doing DNS things.”
The first real requirement is that your VMs must actually use Pi-hole
Pi-hole’s own post-install documentation says this directly: after installation, you need to configure your router so DHCP clients use Pi-hole as their DNS server. If the router cannot do that, Pi-hole can provide DHCP itself, and if neither approach fits, devices can be pointed at Pi-hole manually.
That part matters more than almost anything else.
Because a beautiful local DNS design means nothing if your VMs are still asking some other resolver.
So for a VM network, I think the first practical checkpoint is not “Did I add Unbound?”
It is:
“Who is the VM actually querying for DNS?”
In most sane homelab setups, the answer should be:
- the Pi-hole IP handed out by DHCP on that subnet;
- or the Pi-hole IP configured statically inside the VM if that is your model.
This is what makes the setup real. Pi-hole has to be on the path, not just installed somewhere on the network looking elegant in a web dashboard.
Local VM names should be solved by Pi-hole, not by Unbound
This is the architectural mistake I would avoid first.
Pi-hole’s FAQ explains the order in which locally defined DNS records are used. It lists the device hostname and pi.hole, then custom config files in /etc/dnsmasq.d/, then /etc/hosts, and then the “Local (custom) DNS” list stored in /etc/pihole/custom.list.
That is exactly why I think Pi-hole should own the local names for your VMs.
If your VM is supposed to be:
grafana.labpbs.labdocker.labwiki.lab
then Pi-hole is the natural place to define and serve those names to your clients.
Unbound’s role is not “know your homelab naming scheme better.”
Its role is to recurse for names your local system does not define.
Pi-hole’s config docs also note that the configured DNS domain can be purely local, and Pi-hole may answer locally from its own configuration without forwarding those requests upstream. That is exactly the behavior you want for internal VM names. A VM name that belongs to your local lab should not have to leave your network just to come back as “unknown.”
Unbound is there to make upstream resolution private, local, and cache-friendly
Pi-hole’s Unbound guide documents the common pattern very concretely:
- configure Unbound to listen on
127.0.0.1 - use port
5335 - then set Pi-hole’s custom upstream DNS to
127.0.0.1#5335
The guide explicitly says to untick the other upstream DNS servers and point Pi-hole at that local recursive resolver instead.
That makes the architecture very direct.
Your VMs are not talking to Unbound.
Pi-hole is.
Unbound’s own home-resolver guide also reinforces the shape of this design. It describes Unbound as a validating, recursive, caching DNS resolver and notes that by default it is queriable only from the local host unless you deliberately expose it more broadly.
For the classic Pi-hole-on-the-same-box pattern, that default is actually great. It keeps Unbound as an internal dependency rather than another open resolver floating around your LAN.
That is one reason I like this arrangement so much:
- Pi-hole is the network-facing DNS endpoint for your VM clients;
- Unbound stays behind it as the recursion engine.
That layering feels clean.
The setup becomes truly useful when local names and public resolution both work through one calm path
This is where the whole design starts paying off emotionally.
A VM should be able to ask for:
grafana.labpbs.labexample.comdocs.proxmox.com
through one DNS server from its own point of view.
It should not need one resolver for internal names and another for public names unless you have a very specific advanced reason to split them.
That is why Pi-hole is such a natural front door here. It can answer the local names you define and forward the rest to Unbound, which then performs recursive resolution for public domains. Pi-hole’s own guide describes this delegation path explicitly, and Unbound’s docs explain why running your own resolver keeps caching local and avoids simply outsourcing all recursion to some upstream public provider.
That is not only cleaner.
It also makes debugging much more human.
You know where the client asked.
You know where the local answers should live.
You know where unresolved public questions should go next.
Verification matters more than theory in homelab DNS
This is the part I trust most:
test the path instead of admiring the diagram.
Pi-hole’s Unbound guide gives a very practical verification step. After configuring Pi-hole to use Unbound as upstream, you can query through Pi-hole and then inspect Pi-hole’s log to confirm that it forwarded the request to 127.0.0.1#5335.
Pi-hole’s command documentation also points out that pihole tail is useful for watching DNS queries in real time.
That means a very practical validation flow can be:
- query a local VM name from another VM or client;
- confirm Pi-hole answers it directly;
- query a public domain;
- watch Pi-hole logs or query logs to confirm it forwarded the request to Unbound on
127.0.0.1#5335; - if needed, test Unbound directly with
dig ... @127.0.0.1 -p 5335on the Pi-hole host, exactly as Pi-hole’s guide documents.
This matters because local DNS problems usually do not come from DNS theory.
They come from one broken hop in a simple chain:
- wrong DNS server handed to the client;
- missing local record in Pi-hole;
- wrong Unbound listener or port;
- Pi-hole still pointing at some other upstream;
- the wrong machine doing the asking.
Testing the flow step by step is much more calming than re-reading the UI and hoping.
The most common mistake is trying to make every layer do every job
I think this is the core warning.
If Pi-hole is your client-facing DNS, let it be your client-facing DNS.
If Unbound is your recursive upstream, let it be your recursive upstream.
Do not make Unbound carry the burden of your local VM naming unless you have a very deliberate reason to.
Do not expect Pi-hole local DNS to matter if your VMs are still using your router or hypervisor host as DNS.
And do not confuse “Unbound is installed” with “the VMs are resolving through the intended path.”
This is one of those homelab designs that becomes much easier the moment each layer is allowed to stay boring.
Verification note: Commands, terminology, and support details in this guide were cross-checked against Pi-hole’s official Unbound guide.
For broader context, continue with Installing Proxmox VE for the First Time: A Practical Guide and Understanding Proxmox Storage Types: Local-ZFS, LVM-Thin, and Directory.
Conclusion
Setting up local DNS resolution for VMs with Pi-hole and Unbound becomes much easier once the responsibilities are clean. Your VMs should actually query Pi-hole. Pi-hole should own the local names you care about and answer them from its local DNS configuration. Unbound should sit behind Pi-hole as the recursive upstream, typically on 127.0.0.1#5335, resolving public names that Pi-hole does not answer locally. That layering gives you one calm DNS path for both internal and public lookups without forcing every VM to memorize private IPs forever.
That is the version of local DNS I trust most in a homelab. Not clever, not overbuilt, just readable. Once the clients, local records, and upstream recursion each live in the correct place, VM DNS starts feeling like infrastructure instead of improvisation.
FAQ
Should my VMs query Unbound directly?
Usually no, not in the classic Pi-hole plus Unbound pattern. Pi-hole should stay the client-facing DNS server for your VMs, while Unbound stays behind it as the local recursive upstream.
Where should local VM names like grafana.lab or pbs.lab be defined?
In this setup, Pi-hole is the natural place. Its local DNS records and local DNS behavior are designed to answer internal names directly, while Unbound is better reserved for recursive upstream resolution of public names.



