WireGuard DNS Not Working? Fix Internal Name Resolution Across Your Tunnel
WireGuard doesn’t handle DNS automatically
When a WireGuard client connects, it gets a tunnel IP and a route — nothing more. If you didn’t add a DNS = line to the client config, the device keeps using whatever resolver it had before: your ISP’s server, a public one like 8.8.8.8, or your router. None of those know anything about your internal hostnames. That’s why devices aren’t found.
Step 1: Add DNS= to the client’s [Interface] block
Open the client’s WireGuard config — typically /etc/wireguard/wg0.conf on Linux, or the equivalent screen in your app on Windows, macOS, or Android — and add a DNS line:
[Interface]
Address = 192.168.103.2/24
DNS = 192.168.103.1
PrivateKey = <your-key>
The IP you point DNS at should be a resolver that can see your internal network. The simplest option is the WireGuard server’s own tunnel IP (192.168.103.1 in this case), provided something is actually listening there for DNS queries. More on that below.
Step 2: AllowedIPs must cover the resolver’s address
AllowedIPs is WireGuard’s routing table for the tunnel. Simple rule: if the DNS server’s IP isn’t in that list, the query goes out the wrong interface and the resolver never sees it.
For a split-tunnel setup — where you only route certain subnets through WireGuard rather than all traffic — list every subnet clients need to reach, including the one containing your DNS server. For the network layout described in the original thread:
[Peer]
PublicKey = <server-pubkey>
AllowedIPs = 192.168.103.0/24, 192.168.100.0/24, 192.168.101.0/24, 192.168.102.0/24
Endpoint = your.server.example:51820
Leave any of those subnets out and traffic to hosts on that segment bypasses the tunnel entirely.
Step 3: Run a DNS server on the WireGuard host
A DNS = directive only works if something is actually listening at that address. Three solid options:
- dnsmasq — lightweight, reads
/etc/hostsby default, easy to bind to a single interface. Good first choice for a home lab. - Pi-hole — if you already have one on the LAN, point
DNSat its IP and make sure it’s covered by AllowedIPs. - Unbound — a recursive resolver with fine-grained zone control. More config, more precision.
A minimal dnsmasq config bound to the WireGuard interface:
# /etc/dnsmasq.conf
interface=wg0
bind-interfaces
listen-address=192.168.103.1
Put internal hostnames in /etc/hosts on the same machine and dnsmasq will serve them to every client that connects. No zone config needed for simple setups.
Enable IP forwarding and NAT on the server
Clients can talk to the WireGuard host directly. Reaching other machines on the LAN is a different matter — that requires IP forwarding. Enable it persistently:
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Then add masquerade rules to the server’s wg0.conf. Swap eth0 for your actual LAN-facing interface:
[Interface]
Address = 192.168.103.1/24
ListenPort = 51820
PrivateKey = <server-key>
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Open port 53 from the WireGuard subnet
Easy to forget. The firewall on the WireGuard server needs to allow DNS from the tunnel subnet. With ufw:
sudo ufw allow in on wg0 to any port 53
With iptables directly:
iptables -A INPUT -i wg0 -p udp --dport 53 -j ACCEPT
iptables -A INPUT -i wg0 -p tcp --dport 53 -j ACCEPT
Debugging checklist
If it still doesn’t work after all that, go through these in order:
- Client config has
DNS = <resolver-ip>in[Interface]? - That IP is covered by
AllowedIPsin[Peer]? - DNS server actually running? Test with
dig @192.168.103.1 somehostnamefrom the client. - UDP and TCP port 53 open on the server firewall for the wg0 interface?
- DNS server has entries for your internal hostnames (via /etc/hosts, a local zone, or DHCP lease records)?
- IP forwarding enabled if traffic needs to cross into the LAN subnets?
The most common miss is the third item — people set DNS = correctly but never check whether the resolver is actually listening on that address.
Sources
- wiki.archlinux.org
- procustodibus.com
- engineerworkshop.com
- cr0x.net
- oneuptime.com
- forums.lawrencesystems.com
- en.wikipedia.org
- starsandmanifolds.xyz
