Homelab

Internal DNS for a homelab: the traps nobody warns you about

Your own resolver gives you real names, split horizon and network wide blocking. It also makes you responsible for the most load bearing service on the network.

Running your own DNS is one of the highest value things you can do in a homelab. It gives you real names instead of memorised IP addresses, one place to change when a service moves, network wide ad and tracker blocking, and the split horizon setup that makes internal HTTPS certificates work properly.

It also makes you responsible for the single most load bearing service on your network. When DNS stops, everything stops, and the people you live with do not care that it was a container update.

This is what to get right, and the traps that are not obvious until you hit them.

The shape of the setup

Whatever software you use, the arrangement is the same. Your resolver holds records for local names and forwards everything else upstream. Your DHCP server hands out the resolver's address so clients use it automatically.

Three decisions are worth making deliberately.

Give the resolver a static address. It is handing out addresses, so it cannot depend on getting one. A DHCP reservation is acceptable, a genuinely static address is better, because a reservation still depends on the DHCP server being healthy at the moment the resolver boots.

Set DNS at the DHCP level, not per device. Configure the router's DHCP to advertise your resolver. Configuring each device by hand works until you add a device, which is always.

Decide what happens when it is down before it is down. This is the part people skip, and it is the next section.

Secondary DNS is not failover

This single misunderstanding causes more confusion than anything else in homelab DNS, so it is worth being precise.

When a client is given two DNS servers, it does not use the first and fall back to the second only on failure. Most resolvers query them in whatever order they like, in parallel or alternating, and take the first usable answer. Some remember which was faster. Behaviour varies by operating system and version.

What follows from that:

  • Handing out your ad blocker as primary and the router as secondary does not give you redundancy. It gives you intermittent ad blocking, because roughly half your queries go to the router and get answered normally. Ads appear sometimes. Internal names resolve sometimes. It looks like a flaky network.
  • If both entries point at the same machine, you have no redundancy at all, which is fine as long as you know it.

Three honest options:

Accept the single point of failure. Advertise one resolver, keep it boring, and know that if it dies you change one DHCP setting to restore service. For many homelabs this is the right trade.

Run two resolvers with identical configuration. Two instances on different hardware, same blocklists, same local records. Because clients use both unpredictably, identical configuration is what makes that acceptable rather than annoying.

Use a shared address. Two instances behind one virtual IP with keepalived, so clients see a single address that moves to whichever instance is alive. More moving parts, and the only approach that behaves the way people imagine secondary DNS behaves.

Whichever you choose, write down how to bypass it. A router DHCP setting changed back to a public resolver restores the internet for everyone in two minutes, and that note is worth having somewhere findable when you are not the one troubleshooting.

Query any resolver directly

DNS Lookup takes a custom resolver address, so you can ask your Pi-hole, your router and a public resolver the same question and compare the three answers. DNS Record Explorer shows the full record set with TTLs, which is how you tell a cached answer from a fresh one.

Download on the App Store

DNS rebind protection

Here is a trap that produces a genuinely baffling symptom: your resolver returns the correct private address, and the client still cannot resolve the name.

Many routers implement DNS rebind protection, which discards DNS responses that contain private addresses. The intent is reasonable: it blocks an attack where a hostile site returns a private address to make your browser attack devices on your network. The side effect is that it also blocks the entirely legitimate case of your own resolver answering with your own server's address.

It bites when your router is in the query path, either because it forwards for clients or because your resolver forwards through it. The signature is distinctive: querying your resolver directly gives the right answer, while a client on the network gets nothing.

Most router firmware lets you allowlist specific domains for rebind protection, and that is the correct fix. Turning the protection off entirely works too and gives up a real safeguard.

The same mechanism is why some public resolvers will not return private addresses for your domain, which matters if you ever point public DNS at an internal IP. Keep private addresses in your internal zone only.

The clients that ignore you

You configured DHCP, so every device uses your resolver. Except for the ones that do not.

Browsers with encrypted DNS. Several browsers enable DNS over HTTPS by default in some regions, sending lookups straight to a public resolver over an encrypted channel and bypassing your network's settings entirely. The symptom is unmistakable once you know it: internal names work in one application and fail in another on the same machine, and ad blocking applies everywhere except the browser.

Devices with hardcoded resolvers. Some smart TVs, streaming sticks and IoT devices ship with public resolver addresses baked in and ignore DHCP completely. If you care, the fix is a firewall rule redirecting outbound port 53 to your resolver, which catches plain DNS but not the encrypted kind.

Private Relay and VPNs. Anything that tunnels traffic usually tunnels DNS with it. A device with an always on VPN is not using your resolver regardless of what DHCP said.

Containers. A container gets its resolver from its runtime, not from your DHCP. If a container cannot resolve an internal name while the host can, that is the reason, and it is configured at the runtime or compose level.

Reverse lookups, so scans show names

A small piece of configuration with a disproportionate payoff.

Forward lookups turn names into addresses. Reverse lookups do the opposite, and almost nobody sets them up at home. The result is that every diagnostic you run shows bare IP addresses and you have to remember which is which.

If your DHCP server and your resolver are the same software, this is usually one setting. If they are separate, most resolvers support conditional forwarding: reverse queries for your subnet get sent to the router, which knows the DHCP leases and can answer them.

Once it works, a LAN scan comes back as a list of names instead of a list of numbers, a traceroute labels your own hops, and a reverse lookup on an address you do not recognise tells you what it is immediately. For ten minutes of setup it makes every subsequent investigation faster.

Testing it properly

The useful technique is always the same: ask the same question of several resolvers and compare the answers.

  1. Query your resolver directly

    Use the custom server field in DNS Lookup and point it at your resolver's address. This tests the resolver itself, independent of what any client is configured to do.

  2. Query a public resolver with the same name

    1.1.1.1 or 8.8.8.8. For an internal name this should return nothing. If it returns your private address, your internal records are published publicly and that is worth fixing.

  3. Query whatever the client is actually using

    If the direct query works and the client fails, the problem is not your resolver. It is rebind protection, encrypted DNS in the application, a VPN, or a container's own resolver configuration.

  4. Confirm the resolver is listening at all

    A UDP connect with the DNS profile on port 53 confirms the service is up before you debug records. If the container restarted into a broken state, this tells you in two seconds.

  5. Read the TTL on the answer

    DNS Record Explorer shows it. A counting down TTL means you are looking at a cached answer and the resolver will refresh it when it expires, which tells you exactly how long to wait after a change rather than guessing. The same logic as public DNS propagation, on a smaller stage.

Worth chaining those into one automation pointed at your resolver and running it after every change to the DNS container. It is a thirty second check that catches a broken deployment before anyone else in the house notices.

The short version

Give the resolver a static address, advertise it through DHCP, and decide in advance what happens when it dies, because a second entry pointing at your router is not failover and will give you intermittent behaviour instead. Watch for rebind protection discarding private answers and for browsers and devices that bypass your resolver entirely. Set up reverse lookups so scans show names. And when something breaks, ask your resolver and a public one the same question, because the difference between the two answers is the diagnosis.

Frequently asked questions

Is a secondary DNS server a backup?

No. Clients do not use the first server and fall back to the second only on failure. Most query both in whatever order they like and take the first usable answer, so a second entry pointing at your router gives you intermittent behaviour rather than redundancy.

Why do ads appear sometimes even though Pi-hole is running?

Almost certainly because a second DNS server is advertised alongside it. Roughly half your queries go to that other server and are answered normally. Advertise only the blocking resolver, or run two instances with identical configuration.

My DNS server returns the right private address but clients cannot resolve it. Why?

DNS rebind protection on your router discards responses containing private addresses. Querying the resolver directly works while clients get nothing. Allowlist your internal domain in the router's rebind protection settings rather than disabling it entirely.

Why do internal names work in one app but not my browser?

The browser is probably using DNS over HTTPS, which sends lookups directly to a public resolver and bypasses your network's DNS settings. Ad blocking will also appear to work everywhere except that browser.

Why can my containers not resolve internal hostnames?

A container gets its resolver from the container runtime, not from your network's DHCP. It has to be configured at the runtime or compose level, which is why the host can resolve a name that a container on the same machine cannot.

How do I make network scans show device names instead of IP addresses?

Set up reverse DNS. If your DHCP server and resolver are the same software this is usually one setting. Otherwise use conditional forwarding so reverse queries for your subnet go to the router, which knows the current DHCP leases.

What should I use as my internal domain name?

A subdomain of a real domain you own. Avoid .local, which is reserved for multicast DNS and which some operating systems refuse to send to a DNS server at all, and avoid invented suffixes like .home or .lan that could be delegated as real top level domains.