WireGuard connects, your IP shows as home, and then you notice DNS queries still hit your ISP. A working tunnel isn’t a trustworthy VPN until you lock down routing, DNS, and a kill switch. Here’s how.
The Setup That Almost Works
A minimal config looks like this on the server:
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <server-private-key>
[Peer]
PublicKey = <client-public-key>
AllowedIPs = 10.0.0.2/32
On the client, AllowedIPs = 0.0.0.0/0 routes all traffic through the tunnel. After wg-quick up, a ping to 10.0.0.1 succeeds and external sites show your home IP. That’s a routable tunnel, not a VPN. DNS is untouched, and if the tunnel drops, packets leave the client’s physical interface without complaint.
Enable IP forwarding on the server. Without it, the server won’t route packets from the WireGuard interface to the outside world. Set it permanently in /etc/sysctl.conf:
net.ipv4.ip_forward=1
Apply with sysctl -p. Skipping this is the top reason a freshly configured WireGuard “VPN” can’t reach the internet.
Routing Traffic Properly with iptables
To make the server a gateway, add NAT masquerading so return traffic finds its way back:
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
This rewrites the source address of packets leaving eth0 to the server’s own IP. Without it, reply packets destined for 10.0.0.2 get dropped by the first router that doesn’t know about your private subnet.
Most distributions default the FORWARD chain to DROP, so explicitly allow forwarding between the WireGuard interface and the external interface:
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT
A common mistake is omitting the -o interface match in the masquerade rule. A bare -j MASQUERADE will masquerade all traffic, including between WireGuard peers, breaking internal routing.
Use PostUp and PostDown hooks to apply and remove these rules automatically. In the server’s [Interface] section:
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
%i expands to the interface name (wg0). The rules appear when the interface comes up and disappear when it goes down, keeping firewall state predictable.
The DNS Leak You’ll Miss
Even with all traffic routed through the tunnel, the client’s DNS resolver often remains set to the local network’s default — typically your router at 192.168.1.1 or a DHCP-supplied address. Those queries bypass the tunnel and go straight to your ISP. That’s a DNS leak, and it tells your ISP every site you visit.
Fix it on the client first. In the client’s [Interface] section, add:
DNS = 10.0.0.1
This tells wg-quick to reconfigure /etc/resolv.conf (or use systemd-resolved) to point at the server’s WireGuard IP. When the tunnel goes down, wg-quick restores the original settings. Linux clients running systemd-resolved often ignore the DNS directive because resolv.conf is a symlink managed by systemd. You can force wg-quick to use resolvconf or set DNS = and then manually run resolvectl dns wg0 10.0.0.1. Better yet, don’t rely on client-side configuration alone.
On the server, catch misconfigured or uncooperative clients by redirecting all outbound DNS traffic to a resolver you control. Any UDP or TCP packet on port 53 from the WireGuard subnet gets intercepted and sent to a local resolver like Unbound or a trusted public resolver.
Forcing DNS with iptables
Add these DNAT rules on the server, ideally in PostUp:
iptables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.0.0.1:53
iptables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.0.0.1:53
This assumes the server runs a resolver on 10.0.0.1. If you’re forwarding to an external resolver like 1.1.1.1, set the destination IP accordingly, but be aware the server will then forward the query from its own external interface. The PREROUTING chain sees packets before routing decisions, so interception happens before they exit the server.
If you run a local resolver like Unbound, ensure it listens on the WireGuard interface. By default, many resolvers listen only on localhost. Edit /etc/unbound/unbound.conf:
interface: 10.0.0.1
access-control: 10.0.0.0/24 allow
Verifying No Leaks
Test with dig from the client, targeting a domain you haven’t visited recently:
dig example.com
On the server, run tcpdump -i any port 53 to see where queries actually go. Packets from the client’s WireGuard IP hitting your resolver mean you’re clean. Packets heading out eth0 to an ISP resolver mean a leak.
Building a Real Kill Switch
A kill switch prevents any traffic from leaving the client if the VPN tunnel drops. Apply these iptables rules on the client:
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -j DROP
This allows loopback traffic, packets belonging to existing connections (so established sessions aren’t cut mid-stream), and new outbound traffic on wg0. Everything else gets dropped. These rules belong on the client, not the server; the server is a gateway handling multiple peers and can’t restrict its own outbound traffic that way without breaking routing.
Automate with PostUp and PostDown in the client’s WireGuard config:
[Interface]
...
PostUp = iptables -A OUTPUT -o lo -j ACCEPT; iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT; iptables -A OUTPUT -o %i -j ACCEPT; iptables -A OUTPUT -j DROP
PostDown = iptables -D OUTPUT -o lo -j ACCEPT; iptables -D OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT; iptables -D OUTPUT -o %i -j ACCEPT; iptables -D OUTPUT -j DROP
When the tunnel goes down, wg-quick runs PostDown, removing the restrictive rules and restoring normal network access. Without PostDown, the kill switch stays permanently enabled, blocking all traffic.
The Lockout Risk
The kill switch is sharp. If you apply these rules without PostDown cleanup, or if you manually add them and the VPN fails, you lose all network access on that machine. Remote servers become unreachable, and you can’t SSH in to fix it without out-of-band console access. Always test PostDown by bringing the tunnel down and verifying connectivity returns. When experimenting, schedule an at job to flush iptables in 5 minutes as a safety net: echo "iptables -F" | at now + 5 minutes. That way, lockout is temporary.
The kill switch alone won’t prevent DNS leaks if the resolver is reachable outside the tunnel. Combine it with the DNS enforcement from the previous section.
A trustworthy WireGuard VPN needs more than a handshake. Routing, DNS enforcement, and a client-side kill switch work together to prevent traffic from leaking when the tunnel is up or when it fails. The config snippets above are small enough to audit and understand, and they stick to boring, proven iptables that won’t surprise you. Apply them, test with tcpdump, and sleep better.