When the kill switch drops the tunnel, it takes the DNS path with it, and the resolver left behind is rarely the one you expect. Two designs handle that moment very differently: block every DNS packet that is not inside the tunnel, or leave one resolver standing as an exception. Both are defensible. From a browser tab the symptoms look almost identical, so the first job is to work out which design you actually deployed.

I keep that distinction in front of me because the fix is opposite in each case. One design needs a wider exception; the other needs a narrower one. Guessing costs you either privacy or an afternoon.

Two DNS designs for the same outage

Design A treats the resolver as just another service behind the tunnel. Firewall rules allow UDP and TCP 53 on the tunnel interface only, and the address handed to the client is a resolver inside the tunnel. When the tunnel drops, lookups stop, and that is the intended result rather than a fault.

Design B keeps a resolver reachable on the local network, usually the router at 192.168.1.1 or the operating system stub at 127.0.0.53, on the reasoning that name resolution should not die with the tunnel. It is convenient on a home LAN where the router resolves for the whole house. On hotel or airport Wi-Fi the same exception points at someone else's server, and every name you type is disclosed even though your web traffic stays blocked.

The boundary is who owns the resolver. If you trust the local one, an exception is a small, honest hole. If you do not, the tunnel-only design is the only one that holds, and the price is that a dropped tunnel takes every name with it.

Why the VPN client's own lookup still works

The client has to resolve the gateway hostname before a tunnel exists, so a strict block must not cover the client's bootstrap path. Most clients either cache the endpoint address or ship a documented bootstrap exception for that single host. If your rule set blocks that lookup, the tunnel never comes back and the logs show a client that cannot reach its own server, which is a policy defect rather than a DNS defect.

Split DNS is the other place this shows up. The client pushes an internal zone to a private resolver and leaves everything else with the system resolver. After a drop, public names may still resolve while internal ones time out, because the private zone has no route left. To the user that is "the VPN broke the internet"; to the resolver log it is a query that never arrived.

Under systemd-resolved you can see the split directly. resolvectl status lists the per-link DNS servers and routing domains, and after the tunnel goes down the internal domain keeps pointing at a server on a link that no longer exists. The stub at 127.0.0.53 still accepts the query, then waits on an upstream that will not answer, which is why the failure takes seconds instead of returning at once.

Encrypted DNS does not get around a packet filter

DNS over HTTPS and DNS over TLS travel on 443 and 853, so a rule that blocks port 53 has said nothing about them. Blocking "DNS" by port is a policy about a port, not about name resolution. If your kill switch blocks all traffic outside the tunnel, DoH is covered by accident. If you carved out an allowance for a captive portal or a public resolver, it is not.

Client-side controls are the cheaper lever. Firefox drops its DoH resolver when the canary name use-application-dns.net does not resolve, which is why managed networks publish that domain as NXDOMAIN; enterprise policy can pin it off as well. Android Private DNS and Chrome's secure DNS have their own switches and their own defaults. Turning those off is more dependable than chasing resolver addresses, because the address list changes and your rule set will not.

There is a version of this that is not about leaks at all. An application with a pinned resolver still needs a network path to reach it. Encryption changes who can read the query, not whether the packet can leave the machine, so a whole-device block defeats the setting without touching it.

Match the symptom to the layer

Most reports reach you as one sentence, and that sentence usually names the layer already. The table below is what I run through before touching a rule; the point is to decide whether the query was blocked, misdirected, or never sent.

SymptomLikely layerFirst check
Every name fails at once, instantlyResolver answering with a refusalQuery the resolver by address, not by name
Lookups hang for seconds, then time outPackets dropped with no responseWatch port 53 on the physical link
Public names resolve, internal ones do notSplit-DNS routing domainPer-link DNS servers on the tunnel
Names resolve, the page still failsNot DNS: routing or MTUConnect to the IP directly
Only one application failsPinned resolver or cached answerCheck that app's DNS setting

The first two rows are the ones people misdiagnose. A silent drop and a refusal both look like "DNS is broken" in a browser, but only one of them means your kill switch is doing its job. A refusal means something answered, so the block is not where you think it is.

Restore the resolver policy, then prove it

Fix the design, not the symptom. For tunnel-only resolution, allow 53 on the tunnel interface, push the internal resolver with the tunnel configuration, and leave the physical link with no DNS allowance. With wg-quick the DNS = line is what writes the resolver into the interface configuration, so a tunnel that omits it leaves the old resolver in place and every query on the wrong path. The wg-quick manual documents that behaviour per platform.

Then verify in this order. On Linux, resolvectl status shows the active link, dig +short example.com tests a public name, dig @10.8.0.1 internal.example.com tests the private one, and sudo tcpdump -ni any port 53 shows which interface the packets use while you run both. On Windows, Get-DnsClientServerAddress -AddressFamily IPv4 and Resolve-DnsName example.com give the same two facts: who is configured, and who answers.

Finish with the negative test. Disconnect the tunnel, confirm that a fresh public name fails, confirm that the private name fails, and confirm that no port 53 packet left the physical adapter. A block that holds only for cached answers is not holding at all.

Checklist before you close the ticket:

  • The resolver in use during the outage is the one the policy intends.
  • The client's own endpoint lookup is still allowed, by cache or by exception.
  • DoH and DoT are either covered by the block or disabled by policy.
  • Private zones fail closed, not open, once the tunnel is gone.

Artihkkalat mat laktásit