7 min read

Locking Down DNS on Your Home Network

Locking Down DNS on Your Home Network

Your local DNS filter is probably being bypassed right now. And you don't even know it.

Here's the thing about running a local DNS resolver like AdGuard Home or Pi-hole. You set it up, you add your blocklists, you feel good about yourself. No more ads, no more trackers, full control over your network's DNS.

Except you don't have full control. Not even close.

Your Google Home is ignoring your DHCP settings and sending DNS straight to 8.8.8.8. Your browser is wrapping DNS queries in encrypted HTTPS so your resolver can't even see them. Your phone's apps are connecting to hardcoded DNS server IPs, skipping hostname resolution entirely. That query for ads.tracking-nightmare.com? It's getting resolved somewhere you don't control, and your carefully curated blocklists never even see it.

There's a whole family of ways devices bypass your local DNS:

  • Hardcoded DNS servers: Some devices (Google Home, Chromecast, Android TV) have 8.8.8.8 baked in. They ignore whatever DNS server your DHCP hands out.
  • DoH (DNS-over-HTTPS): Wraps DNS queries inside normal HTTPS traffic on port 443. Your firewall can't tell the difference between someone browsing google.com and someone resolving dns.google. It's all just encrypted HTTPS.
  • DoT (DNS-over-TLS): Encrypts DNS queries over a dedicated TLS connection on port 853. Unlike DoH, it doesn't try to hide in regular web traffic. That makes it easier to block (just drop port 853), but it still sends your queries to an external resolver your local DNS never sees.
  • DoQ (DNS-over-QUIC): DNS over the QUIC transport protocol, running on UDP 853 (standard) with 443 as an optional alternative. Faster than DoT because QUIC eliminates the TLS handshake overhead. Still new, but Android 11+ already supports it natively through Private DNS settings.

So What Does This Look Like in Practice?

Standard DNS runs on port 53, completely unencrypted. That's what makes it so easy to filter. Your resolver sits in the middle, sees every query, and decides what gets through.

You think your network looks like this:

Device โ†’ DNS query (port 53) โ†’ Your resolver โ†’ Filtered response โœ“

Here's what's actually happening behind your back:

Chromecast   โ†’ DNS query (port 53)  โ†’ 8.8.8.8 directly       โ†’ Unfiltered โœ—
Firefox      โ†’ HTTPS request (:443) โ†’ cloudflare-dns.com      โ†’ Unfiltered โœ—
Android app  โ†’ TLS connection (:853) โ†’ dns.google             โ†’ Unfiltered โœ—

Your resolver: "Nobody asked me anything."

Three different bypass methods. All happening at the same time. Your blocklists might as well not exist.

The Setup: OPNsense + AdGuard Home + Unbound

Before we start locking things down, here's how the DNS stack on my network works.

Every device on my network sends its DNS queries to AdGuard Home. That's the first stop. DHCP hands out its IP as the network's DNS server, so all queries hit AdGuard on port 53. If the domain is on a blocklist, AdGuard kills the query right there. If it's cached, AdGuard returns the answer immediately without going any further.

If the query passes filtering and isn't cached, AdGuard forwards it to Unbound, which runs on OPNsense on port 5353. Unbound doesn't forward to Cloudflare or Google or any third-party resolver. It resolves everything recursively, starting from the root DNS servers, walking down to the TLD servers, then to the authoritative server for that domain. Three hops, all done by Unbound itself, and the answers get cached so the next lookup is instant.

OPNsense wraps the whole thing as the firewall. It controls what traffic enters and leaves the network. The NAT rules, port blocks, and IP blocklists we're about to set up all live here. It's the enforcement layer that makes sure devices can't just ignore AdGuard and resolve DNS on their own.

The Defense-in-Depth Approach

There's no single rule that blocks all of this. You need layers. Here's what I implemented on this setup, and the thinking behind each step.


Step 1: Redirect All Standard DNS

The problem: Some devices (Google Home, Chromecast, Android TV) have DNS servers hardcoded to 8.8.8.8. They ignore your DHCP-assigned DNS entirely.

The fix: A NAT port forward rule that intercepts all DNS traffic (port 53) and redirects it to your local resolver. Regardless of where the device thinks it's sending it.

NAT Port Forward:
  Interface: LAN
  Protocol: TCP/UDP
  Destination: ! LAN Address (anything NOT the firewall itself)
  Destination Port: 53
  Redirect Target: 172.16.70.227 (AdGuard Home)
  Redirect Port: 53

The key detail: exclude your resolver from the redirect, or you'll create a loop.

After this: A device sending DNS to 8.8.8.8 silently gets redirected to your AdGuard Home. It has no idea. It thinks Google answered.


Step 2: Block DNS-over-TLS (Port 853)

The problem: DoT uses a dedicated port: 853. Any device or app using DoT will bypass your local DNS.

The fix: This one's dead simple. Block port 853 outbound.

Firewall Rule:
  Action: Block
  Protocol: TCP/UDP
  Destination Port: 853
  Description: Block DNS-over-TLS and DNS-over-QUIC

That's it. One rule. DoT is dead on your network. DoQ on its standard port too.


Step 3: Block QUIC (UDP 443)

The problem: QUIC (HTTP/3) runs on UDP port 443. DNS-over-QUIC can also piggyback on this port. And some browsers will prefer QUIC for DoH connections.

The fix: Block UDP traffic on port 443.

Firewall Rule:
  Action: Block
  Protocol: UDP
  Destination Port: 443
  Description: Block QUIC/HTTP3 and DNS-over-QUIC

The trade-off: This kills HTTP/3 for all web traffic on your network. Browsers fall back to HTTP/2 over TCP transparently. You won't notice a difference. Maybe a few milliseconds slower on some sites. Worth it.


Step 4: Block Known DoH Domains via DNS

The problem: Most DoH clients first resolve the DoH server's hostname via regular DNS. Firefox looks up cloudflare-dns.com, Chrome resolves dns.google, and so on. All through your local resolver.

The fix: If your resolver returns a blocked response for those hostnames, the client can't find the DoH server in the first place.

I added HaGeZi's Encrypted DNS Bypass Blocklist to AdGuard Home. It's the most popular list on GitHub (19k+ stars) with 3,500+ DoH server domains.

AdGuard Home โ†’ Filters โ†’ DNS Blocklists โ†’ Add:
  URL: https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/doh.txt
  Name: HaGeZi DoH Blocklist

After this, queries for cloudflare-dns.com, dns.google, dns.quad9.net, doh.opendns.com, and thousands of others return nothing.

The limitation: This only works if the client resolves the hostname through your DNS first. If a client has the DoH server's IP address hardcoded (like 1.1.1.1), this won't help. That's what Step 5 is for.


Step 5: Block Known DoH Server IPs at the Firewall

The problem: Some apps bypass DNS entirely and connect to DoH servers by IP address. For example, certain apps and IoT devices have hardcoded DoH server IPs baked in, so blocking the hostname at the DNS level isn't enough.

The fix: A URL Table alias in OPNsense loaded with known DoH server IPs, and a firewall rule blocking all traffic to those IPs.

Firewall Alias:
  Name: DoH_Servers
  Type: URL Table (IPs)
  URL: https://raw.githubusercontent.com/hagezi/dns-blocklists/main/ips/doh.txt
  Refresh: Daily

This loads 1649+ IPv4 addresses of known DoH servers. Cloudflare (1.1.1.1, 1.0.0.1), Google (8.8.8.8, 8.8.4.4), Quad9 (9.9.9.9), OpenDNS, NextDNS, AdGuard Public DNS, and hundreds more.

Then a single block rule:

Firewall Rule:
  Action: Block
  Destination: DoH_Servers (alias)
  Description: Block known DoH server IPs

Since this blocks all traffic to these IPs (not just DNS), it also prevents direct HTTPS connections to DoH endpoints.

"Wait, you just blocked 8.8.8.8 and 1.1.1.1. How does your DNS still work?"

Good question. Remember My setup uses Opensense Unbound as a recursive resolver. Instead of forwarding queries to Cloudflare or Google, Unbound resolves everything itself by walking the DNS chain from the top:

Unbound: "Hey root server, who handles .com?"
Root:    "Ask a]gtld-servers.net"

Unbound: "Hey .com server, who handles google.com?"
TLD:     "Ask ns1.google.com"

Unbound: "Hey ns1.google, what's the IP for google.com?"
Google:  "142.251.142.206"

No third-party resolver in the path. Root servers and authoritative name servers aren't on the DoH blocklist, so nothing breaks. AdGuard Home forwards to Unbound, Unbound talks directly to the authoritative sources. You can block every public DNS resolver on earth and your DNS still works.

The alternative is forwarding mode, where Unbound just passes queries to someone like 1.1.1.1 and trusts their answer. Simpler, but now Cloudflare sees every domain you visit. And you'd need to whitelist that IP from the blocklist.

You can check which mode you're running under Services > Unbound DNS > General. If "Use System Nameservers" is unchecked and nothing is configured under Query Forwarding, you're recursive.


What This Doesn't Catch

None of this is bulletproof. Here's where it falls apart.

Meta Apps

Facebook, Instagram, and WhatsApp run their own DoH on star.c10r.facebook.com. That same domain and those same IPs serve regular Facebook traffic. Block them and you break Facebook entirely. That's not an accident. Meta bundles DNS resolution into their main infrastructure specifically so you can't separate it. I checked the HaGeZi blocklist and these domains are intentionally excluded for exactly this reason.

Custom DoH Servers

Anyone can stand up a DoH resolver on a random VPS on port 443. There's no blocklist that will ever catch a private server. If someone on your network really wants to bypass your DNS, they can. This approach only works against the default behavior of browsers, apps, and devices, not against someone actively trying to circumvent it.

VPN Tunnels

If a device connects to a VPN, all your DNS rules become invisible. The traffic is encapsulated inside the tunnel and exits somewhere else entirely. Your firewall never sees the actual DNS queries.

Stale IP Blocklists

Providers change IPs. The list that had 1649 IPs today might be missing new ones tomorrow. Use auto-refreshing URL Table aliases and update daily at minimum. OPNsense handles this natively if you set the alias type to URL Table.

The Bottom Line

The combination of all five steps catches the vast majority of bypass attempts from standard browsers, apps, and IoT devices. The goal isn't perfection. It's making bypass hard enough that most things on your network fall back to your local resolver without anyone having to think about it.