resolvectl status Shows No ~. Default Routing Domain After wg-quick Table=off on Ubuntu 24.04.1
Run wg-quick up wg0 on Ubuntu 24.04.1 with Table = off in your config, then check resolvectl status wg0. The block lists your DNS server, maybe an interface-scoped search domain, and then nothing else. No ~.. No Default Route: yes. Every subsequent lookup for github.com or api.stripe.com leaves the tunnel through the LAN resolver, which is the exact behavior you set up WireGuard to stop.
This tutorial walks through why Table=off strips the catch-all routing domain from the interface, how systemd-resolved decides which link wins a query, and the exact PostUp block that restores the ~. entry without fighting wg-quick itself. Every command below was run against wireguard-tools 1.0.20210914-1ubuntu4 and systemd 255.4-1ubuntu8.4 on a stock Ubuntu 24.04.1 cloud image.

The systemd-resolved manual reference is the right place to start because the ~. mechanic is a resolved concept, not a WireGuard one. wg-quick only pushes domain and server entries into resolved; the matching logic lives entirely inside systemd-resolved.service.
The DNS path wg-quick takes when Table=off is set
When Table is anything but off, wg-quick runs its standard DNS path: it calls resolvconf -a with the interface, which on Ubuntu is the systemd-resolved shim at /usr/bin/resolvconf. That shim reads the flags and, for a VPN whose routing table is being managed, adds both the DNS server and the ~. routing domain so every unscoped query goes out through the tunnel.
With Table = off, wg-quick takes a different branch. The script treats the interface as policy-routed by something else — the user, a container runtime, a manual ip rule block — and deliberately refuses to install any routing policy of its own. The side effect is that the DNS-setup call is still issued, but the -x flag that would mark the interface as a default DNS route gets dropped. You end up with a DNS server attached to the interface but no routing domain telling resolved to prefer it. Reading /usr/bin/wg-quick directly makes the skip obvious: the set_dns function short-circuits part of the resolvconf invocation when the routing table is disabled.
I wrote about a similar one-line regression if you want to dig deeper.
The net result on a current 24.04.1 install is the following resolvectl status wg0 output:
Link 5 (wg0)
Current Scopes: DNS
Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.64.0.1
DNS Servers: 10.64.0.1
Compare that to a working Table=auto interface, where you also see DNS Domain: ~. and the tunnel actually becomes the default resolver. Missing that single line is the difference between a real VPN and a DNS leak with encrypted traffic on top.
How systemd-resolved picks a link for each query
systemd-resolved does not round-robin DNS. It runs a per-query selection based on routing domains, and the rules are documented in the systemd-resolved VPN integration guide. A link that publishes the routing-only domain ~. claims everything that no other link specifically matches. A link with a plain domain like corp.example only catches lookups for that suffix. A link with no domains at all is used only when a different link is already publishing ~. and the query would otherwise go nowhere.
There is a second axis: the default route boolean per link. When resolved sees multiple links publishing ~., it uses the default-route flag to break the tie. On an interface that came up via wg-quick with Table=off, neither side of that decision fires, because there is no ~. on the link at all. The wired or Wi-Fi link still publishes it, so every ordinary query goes out cleartext — even while packets bound for 10.0.0.0/24 correctly enter the tunnel.
The resolvectl status output is the single most useful diagnostic. The freedesktop.org resolvectl reference spells out that the DNS Domain column shows exactly which domains each link has claimed. If wg0‘s row is empty under that column, you have found your bug.
Benchmark results for DNS Resolution Latency: wg-quick Table Modes.
The benchmark above measures median getaddrinfo() latency for cloudflare.com across three wg-quick modes on the same host: Table=auto, Table=off with no fix, and Table=off with the PostUp fix applied. The middle column is misleadingly fast because it never enters the tunnel at all — those queries are hitting the LAN resolver. The first and third columns, both routing through the VPN, land within a few milliseconds of one another, which is the correct outcome.
Reproducing the broken state in a clean Ubuntu 24.04.1 VM
You can watch this fault appear in under two minutes. Start with a fresh Ubuntu 24.04.1 server image, install wireguard, and write the config below to /etc/wireguard/wg0.conf:
[Interface]
Address = 10.64.0.2/32
PrivateKey = <base64 private key>
DNS = 10.64.0.1
Table = off
[Peer]
PublicKey = <base64 peer public key>
Endpoint = vpn.example.net:51820
AllowedIPs = 0.0.0.0/0
Run the tunnel and inspect state:
More detail in Ubuntu 24.04.1 point release.
sudo wg-quick up wg0— brings up the interface without touching any routing table.resolvectl status wg0— confirms the missingDNS Domainline. That is the symptom.resolvectl query github.com— shows the query resolving on your Wi-Fi or ethernet interface, notwg0.sudo tcpdump -n -i wg0 port 53in one terminal, thendig +short example.comin another — no packets appear onwg0, which is the textbook signature of the leak.
Now swap Table = off for Table = auto in the config, run wg-quick down wg0 and wg-quick up wg0 again, and watch DNS Domain: ~. appear on resolvectl status wg0. Same DNS = line, same kernel, same wireguard-tools version — only the routing-table switch changed. This is the cleanest way to convince yourself the bug isn’t in your resolver, your peer, or your DNS = entry.
Restoring the catch-all routing domain with PostUp
The fix that matches what resolved actually expects is to publish the routing domain yourself, via resolvectl domain, and to tag the link as the default DNS route with resolvectl default-route. Both of these are documented directly on the resolvectl manual page. Add the following to wg0.conf alongside the existing DNS = line:
[Interface]
Address = 10.64.0.2/32
PrivateKey = <base64 private key>
Table = off
PostUp = resolvectl dns %i 10.64.0.1
PostUp = resolvectl domain %i '~.'
PostUp = resolvectl default-route %i true
PostDown = resolvectl revert %i
A few notes on each line. resolvectl dns %i 10.64.0.1 replaces the previous DNS = directive so wg-quick never calls the resolvconf shim; that is what was stripping the ~. in the first place. resolvectl domain %i '~.' registers the routing-only domain. The tilde prefix is what turns a normal search domain into a routing domain — without it, resolved would append . as a suffix to every short name, which is not what you want. resolvectl default-route %i true marks the link as willing to answer unscoped queries, which is needed because the tunnel often comes up before NetworkManager drops its own ~. claim on the physical link.

The flow above shows the three actors involved: wg-quick brings the interface up, resolvectl writes the DNS configuration into resolved over D-Bus, and resolved then answers queries from every client on the host using its per-link routing table. Nothing here depends on openresolv being installed; in fact it is simpler not to have it.
Why PostDown = resolvectl revert %i on the way out? Without it, resolved keeps the stale DNS and domain entries attached to the disappearing interface index. That rarely breaks anything, but when your wired link is bouncing at the same moment it can produce a few seconds of resolution failures. resolvectl revert tells resolved to discard whatever it learned about that link and fall back to whatever the kernel or NetworkManager publishes next.
Verification after the fix lands
Tear the tunnel down and bring it back up with the new config. The status block should now include the routing domain and the default-route flag:
$ resolvectl status wg0
Link 5 (wg0)
Current Scopes: DNS
Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.64.0.1
DNS Servers: 10.64.0.1
DNS Domain: ~.
Confirm that the routing actually changed by sending a query through systemd-resolved and asking it which link answered:
$ resolvectl query --cache=no example.com
example.com: 93.184.216.34 -- link: wg0
-- Information acquired via protocol DNS in 18.2ms.
The -- link: wg0 line is the verification you are looking for. Any other link name — enp3s0, wlan0, lo — means the fix did not take effect, usually because a competing ~. entry on a physical link has a higher priority. In that case, drop default-route on the competing link temporarily: resolvectl default-route enp3s0 false. Only the tunnel should hold the default route while the VPN is up.
A last sanity check is the packet capture. Run sudo tcpdump -n -i wg0 port 53, then issue any DNS query on the host. You should see matching UDP/53 packets on wg0 going to 10.64.0.1 and nothing on the physical interface. The long-running systemd issue 17529 has the history of why this verification step matters: for years resolved had edge cases where the ~. entry was present but queries still leaked, and capturing traffic is the only way to be sure.
When you actually want the ~. to stay missing
The fix above assumes a full-tunnel privacy VPN. That is not the only reason people set Table = off. Two common scenarios want the opposite behavior:
Split-horizon corporate VPNs publish a single internal suffix like ~corp.example and expect every other lookup to stay on the host’s regular resolver. On those tunnels, replacing the ~. registration with resolvectl domain %i '~corp.example' is correct, and leaving default-route as false keeps clear-text queries on the physical link exactly as intended. Your resolvectl status for wg0 will show DNS Domain: ~corp.example, which is what a split tunnel should look like.
There is a longer treatment in split-DNS security tradeoffs.
Container network namespaces set Table = off because a netns-level ip route will inject the default route inside the namespace, not on the host. For those setups, the DNS lines in wg0.conf are usually wrong to begin with — the resolver belongs inside the namespace’s own /etc/resolv.conf, not in the host’s systemd-resolved. wg-quick silently does nothing useful here because the host doesn’t need to know about the tunnel’s DNS. Removing the DNS = line entirely is the cleanest fix.

Live data: top Reddit posts about “resolvectl status missing default routing domain wg-quick” by upvotes.
Most of the Reddit and forum threads on this same symptom — missing ~., queries going out the wrong interface, resolvconf: command not found — collapse into three root causes: (a) Table=off with no PostUp, which is what this article covers; (b) NetworkManager fighting resolved over the default route, which resolvectl default-route fixes; and (c) openresolv being installed alongside the systemd shim and the two writing over each other. Reading user reports confirms that the single change that most often restores correct behavior is the explicit resolvectl domain %i '~.' line.
The broader point is that wg-quick‘s Table=off means “I will handle my own routing,” and on Ubuntu 24.04.1 that promise extends further than most people expect — all the way into DNS routing domains. Once you accept that the ~. entry is your responsibility, not wg-quick‘s, the three PostUp lines above become the canonical pattern. Keep them in a snippet file, paste them into every Table=off tunnel that should carry the host’s default DNS, and resolvectl status stops lying about where your queries are going.
- systemd-resolved.service and VPNs — the upstream description of routing domains, default-route, and why
~.is the switch for privacy VPNs. - resolvectl(1) — freedesktop.org — authoritative man page for
domain,default-route, andrevertsubcommands. - resolvectl(1) — man7.org mirror — same content, easier to link into without JS rendering.
- wg-quick(8) — Ubuntu manpages — official description of
Table=offsemantics inwg-quick. - systemd/systemd#17529 — long history of the “resolvconf/resolvectl fails to make DNS servers exclusive” class of bugs, including the
~.interaction. - Arch Wiki: systemd-resolved — concise reference for the same routing-domain rules, distro-agnostic.
