How to Fix DNS Wey No Dey Work for WireGuard
WireGuard tunnel dey up but DNS no resolve, leaks to router, or gets overwritten. Find the three failure modes and the exact fix for each one.
Why DNS dey break as soon as WireGuard tunnel come up
DNS over WireGuard dey fail for three ways, and each one get its own fix. Nothing dey resolve at all, or names dey resolve but queries dey leave your machine outside the tunnel, or the client resolver manager dey overwrite the setting seconds after the interface start. The tunnel almost never be the problem. The problem na the one line wey tell the client which resolver to ask, and the routing wey decide how packets go reach that resolver.
WireGuard dey move IP packets and e no know anything about DNS (domain name system, the service wey turn names like example.com into IP addresses). The DNS = line inside client [Interface] block no be WireGuard setting. wg-quick dey read am, na the shell wrapper wey bring the interface up, and wg-quick then edit the client's resolver configuration while the tunnel dey active and restore am on wg-quick down. So every problem below na routing problem or wg-quick problem, never cryptography problem. If the tunnel never build yet, start with self-hosted WireGuard VPN for your own VPS and come back to this page afterwards.
Confirm say the tunnel dey healthy before you touch DNS at all.
sudo wg show
ping -c 3 10.8.0.1
ping -c 3 1.1.1.1wg show suppose list the peer with recent latest handshake, and both pings suppose answer. If ping 1.1.1.1 times out, you get forwarding or NAT (network address translation) problem, no be DNS problem, and resolver config no go help. Every example here dey use 10.8.0.0/24 as the tunnel subnet and 10.8.0.1 as the server tunnel address. Use your own values instead.
Failure one: nothing dey resolve, because resolver no dey answer
The symptom dey clear. ping 1.1.1.1 dey work, and curl https://example.com dey return this:
curl: (6) Could not resolve host: example.comAsk the tunnel resolver directly from the client. dig dey come from the dnsutils package for Ubuntu and Debian.
dig +short @1.1.1.1 example.com
dig +short @10.8.0.1 example.comThe first command dey return an address, wey prove say packets dey reach internet through the tunnel. The second one no dey return anything and e dey print ;; communication timed out; no servers could be reached. Na the complete diagnosis be this: your client dey point to 10.8.0.1, and 10.8.0.1 no dey answer for UDP port 53.
Two causes fit produce am. Either no resolver dey run for the server, or server firewall dey drop the query before e arrive. Check both for the server.
sudo ss -ulnp | grep ':53'
sudo nft list rulesetResolver wey dey run and bind correctly go show line with 10.8.0.1:53 or 0.0.0.0:53. For Ubuntu, the surprise normally be 127.0.0.53:53: na the systemd-resolved stub listener be that. E dey bind loopback address, and other machines no fit reach am by design. If you point VPN client to server wey only get that stub as resolver, timeout like this one go happen.
The fix na resolver wey dey listen on the tunnel address, plus one firewall rule wey allow peers reach am.
sudo apt update && sudo apt install -y unbound
printf 'server:\n interface: 10.8.0.1\n access-control: 10.8.0.0/24 allow\n' \
| sudo tee /etc/unbound/unbound.conf.d/wireguard.conf
sudo systemctl restart unbound
sudo ss -ulnp | grep '10.8.0.1:53'Then open the port for tunnel traffic only. With nftables, add these two lines to the input chain for /etc/nftables.conf and reload with sudo systemctl reload nftables.
udp dport 53 iifname "wg0" accept
tcp dport 53 iifname "wg0" acceptWith ufw, sudo ufw allow in on wg0 to any port 53 dey do the same work. Never open port 53 to public internet. Scanners go find open recursive resolver within days and use am to amplify denial of service attacks, and your provider go notice that traffic before you notice am.
Run dig +short @10.8.0.1 example.com again from the client. Address for the output mean say resolver path dey work, so na only client remain to use am. Add the line to client [Interface] block and restart the interface with sudo wg-quick down wg0 && sudo wg-quick up wg0.
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1Failure two: DNS leak, because split tunnel resolver traffic no dey route
This one worse, because everything fit look like say e dey work. Names dey resolve, pages dey load, and the queries dey pass as cleartext through the local network wey you no want trust.
Two configurations dey cause am. The first na client wey get AllowedIPs = 0.0.0.0/0, ::/0 but e no get DNS = line. wg-quick dey install the default route for its own routing table and add rule with suppress_prefixlength 0. This one dey intentionally keep more specific local routes active, so the machine fit still reach its printer. The resolver wey client learn through DHCP, usually the router for 192.168.1.1, match one of those local routes. Your traffic dey pass through the tunnel. But the local network still dey receive the complete list of names wey you look up.
The second one na split tunnel: AllowedIPs = 10.8.0.0/24 with DNS = 9.9.9.9. Because 9.9.9.9 no dey inside AllowedIPs, the client no get route to am through the tunnel. So the query dey leave through the local link, exactly as e happen for the first case.
Prove which resolver really dey answer. whoami.akamai.net na public test name wey dey answer with the IP address of the recursive resolver wey ask am. This one let you compare the answer with your server public address.
resolvectl status
dig +short whoami.akamai.net
sudo tcpdump -ni any -c 10 port 53resolvectl status dey print one block for each link. If the block for your ethernet or wireless link still show Current DNS Server: 192.168.1.1, while the wg0 block show nothing, na the leak be that. If dig +short whoami.akamai.net return your home broadband address instead of your server address, e confirm am from the far end. The tcpdump line na the proof wey settle the matter: healthy output dey put every port 53 packet on wg0, while leak dey put dem on wlan0 or enp3s0.
The fix get two parts, and you need both. Set DNS to an address wey dey inside the tunnel, and make sure say the address dey inside AllowedIPs.
[Interface]
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 10.8.0.0/24, 10.20.0.0/1610.8.0.1 dey inside 10.8.0.0/24, so the query go encrypt and send to the server. If you insist on using public resolver for split tunnel, add am as host route: AllowedIPs = 10.8.0.0/24, 9.9.9.9/32. The packets go then pass through the tunnel, but the local network fit still see say you choose that provider from earlier sessions. Resolver wey you run by yourself go avoid this issue.
Resolver assignment na one clear difference between hand-built WireGuard and coordinated mesh, and e dey part of the tradeoff for WireGuard compared with Tailscale. If you run a self-hosted Headscale control server, you go get that coordination without giving your key material to third party. If na that last point dey worry you, remember say Tailscale never holds the keys that encrypt your traffic. The more important question na wetin compromised coordination server or stolen identity account fit add to your network.
Failure three: resolvconf and systemd-resolved dey fight for Linux clients
macOS, Windows, iOS and Android clients dey apply DNS = through the official app and dem no dey cause much wahala. Linux na where the setting dey apply through shell script wey must guess which one among different resolver managers you dey run.
The first failure dey loud. sudo wg-quick up wg0 stop with:
resolvconf: command not foundwg-quick dey call resolvconf, but that binary no dey installed. Install the implementation wey dey talk to systemd-resolved, then bring the interface up again.
sudo apt install -y openresolv
sudo systemctl restart systemd-resolved
sudo wg-quick down wg0 && sudo wg-quick up wg0
resolvectl status wg0The second failure quiet, and na this one fit waste one evening. The interface come up, resolvectl status wg0 correctly show DNS Servers: 10.8.0.1, but lookups still dey go to the old resolver. systemd-resolved dey keep separate resolver list for each link and e dey choose one link for each query. Unless you mark one link as the default route for names, e go continue use the wireless link resolver, because that link get search domain and your own no get.
Set the resolver and claim the default route for the same step. %i go expand to the interface name, so this block go work unchanged for any interface.
[Interface]
Address = 10.8.0.2/32
PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~.
PostDown = resolvectl revert %iDelete the DNS = line when you use PostUp this way, because if not, two mechanisms go write resolver state and na only one of dem go clean up afterwards. The ~. argument na the important half: e mark wg0 as the routing domain for every name, so systemd-resolved go send all queries there instead of choosing link for each query. Verify am.
resolvectl status wg0Healthy output get DNS Servers: 10.8.0.1 and Default Route: yes. If Default Route read no, the resolvectl domain half no run, and you don reach link selection again.
One more case dey worth naming. If /etc/resolv.conf na real file instead of symlink to /run/systemd/resolve/stub-resolv.conf, something else dey control am, usually NetworkManager or container runtime. Run ls -l /etc/resolv.conf before you debug anything else, because tool wey dey rewrite that file whenever network change happen go undo your work for the least helpful moment.
Upgrade: your own filtering resolver through the tunnel
Once queries dey reliably pass through the tunnel, resolver for the far end become control point. If you run AdGuard Home there, every connected device go get blocklist filtering and query log, without client software or configuration for each device. Official install script, wey dem check for July 2026, na one line.
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -vFor first run, setup wizard dey listen on port 3000. Reach am through the tunnel for http://10.8.0.1:3000 instead of opening that port publicly. For the wizard, set both DNS listen address and admin listen address to 10.8.0.1. If unbound from failure one still dey use the same address, stop am first with sudo systemctl disable --now unbound, because two processes no fit bind UDP port 53 for one address. The second process go exit with listen udp 10.8.0.1:53: bind: address already in use.
Client configs no need change if dem already say DNS = 10.8.0.1. Query log go now show every lookup from every peer. This na real privacy decision, no be free benefit: you move trust from your internet provider go yourself, and na you go maintain patches for that box. Server wey internet fit reach need the basic security setup first, and the first ten minutes for a new VPS covers dem.
FAQ
Why my WireGuard tunnel connect but names no resolve?
The tunnel dey carry packets, but e no handle names at all. So if tunnel dey work but lookups no work, na the resolver wey you point to no dey answer. Test am with dig +short @10.8.0.1 example.com from the client. If reply na communication timed out, e mean say either no resolver dey listen on that tunnel address, often because systemd-resolved stub dey bind only to 127.0.0.53, or server firewall dey drop UDP port 53 wey dey enter through wg0. Fix the listener first, then open the port for wg0 only.
How I fit check whether my DNS dey leak through WireGuard?
Run sudo tcpdump -ni any -c 10 port 53 for the client and watch the interface column while you browse. Every packet suppose dey on wg0. If dem show for your wireless or ethernet interface, the queries dey leave as cleartext. dig +short whoami.akamai.net go give you second confirmation, because e answers with the public address of whichever recursive resolver make the request. So if the answer no be your server address, e confirm say leak dey happen.
I need DNS = line if I dey use split tunnel?
Yes. The resolver address must also dey inside AllowedIPs, otherwise the client no get route reach am. With AllowedIPs = 10.8.0.0/24, resolver wey dey for 10.8.0.1 dey covered and the query dey encrypted. Public resolver like 9.9.9.9 no dey covered, so the query go leave through the local link even though DNS line look correct.
Why resolvectl show the correct server but lookups still dey go somewhere else?
systemd-resolved dey keep one resolver list for each link and e choose link for each query. So correct entry for wg0 go dey ignored while another link get the default route for names. Add PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~. to the client [Interface] block and remove DNS = line. After that, resolvectl status wg0 suppose report Default Route: yes.
Which client I suppose fix first when several ones dey broken?
Fix one Linux client first, because na only that platform go show you how the mechanism dey work. resolvectl status and tcpdump go tell you which resolver answer and which interface carry the packet. Phone and desktop apps dey apply the same DNS and AllowedIPs values without showing the underlying setup. So once the Linux client correct, you dey copy configuration wey you don already verify.