WireGuard: route to your home LAN
Your WireGuard handshake works but no host on the home LAN answers. Four settings decide whether a packet reaches 192.168.20.10 and finds its way back.
Why your home LAN does not answer over WireGuard
To route to your home LAN over WireGuard, four separate settings have to agree. Three out of four still gives you a tunnel that looks perfectly healthy, which is what makes this failure so confusing. wg show reports a recent handshake, ping 10.8.0.1 answers in a few milliseconds, and ping 192.168.20.10 returns nothing at all.
Here is the whole list, in the order a packet meets it. LAN means local area network, the private network behind your home router.
AllowedIPson the client must cover the remote subnet, or the packet never enters the tunnel.AllowedIPson the server must cover the client's tunnel address, or the packet is dropped as soon as it is decrypted.net.ipv4.ip_forwardmust be1on the server, because Linux drops any packet that is not addressed to the box itself.- The LAN must know a route back to
10.8.0.0/24, through a masquerade rule on the server or a static route on the home router.
Every one of these fails without an error message. Nothing is logged, nothing warns you, and the handshake keeps working the whole time. Check them in order and the broken one falls out in about a minute.
The network this guide uses
Every address below is an example. Replace them with your own, and keep the replacement consistent, because a half-updated config is the second most common cause of this problem.
- The home LAN is
192.168.20.0/24. The home router is192.168.20.1. - The WireGuard server is a Linux box on that LAN. Its LAN interface
enp1s0holds192.168.20.5, and its tunnel interfacewg0holds10.8.0.1. - The host you want to reach is a NAS (network attached storage) at
192.168.20.10. - The client is a laptop somewhere else,
10.8.0.2inside the tunnel.
The server is a machine on the LAN, not the router itself. That is the normal case, a Raspberry Pi or an old mini PC. It matters for rule 4: your router does not know the tunnel exists unless you tell it, and every host on the LAN sends off-subnet traffic to that router.
If your home connection has no public IP address, none of this works on its own, because nothing on the internet can open a handshake to your house. The section on putting a VPS in the middle covers that case. The same four rules apply there, with one extra peer to keep straight.
AllowedIPs means two different things
One setting does two jobs, and reading it the same way on both ends is the mistake behind most of these tickets. WireGuard calls the mechanism cryptokey routing, described in more detail in how WireGuard binds public keys to IP ranges.
Read outbound, AllowedIPs is a routing table. wg-quick turns each entry into a route pointing at wg0. A packet for 192.168.20.10 is encrypted and sent to a peer only if some peer claims a range that contains that address. List only 10.8.0.0/24 and your laptop sends LAN traffic out of the local Wi-Fi instead, where it either dies or reaches a completely different 192.168.20.10.
Read inbound, AllowedIPs is an access control list. After WireGuard decrypts a packet from a peer, it checks the inner source address against that peer's AllowedIPs and drops the packet if it does not match. There is no log line and no counter for that drop. The packet simply disappears.
So the two config files are never mirror images. The client lists what it wants to reach through the server. The server lists what that client is allowed to use as a source address.
The matched pair of config files
Client, /etc/wireguard/wg0.conf:
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
[Peer]
PublicKey = <server public key>
Endpoint = home.example.com:51820
AllowedIPs = 10.8.0.0/24, 192.168.20.0/24
PersistentKeepalive = 25Server, /etc/wireguard/wg0.conf:
[Interface]
PrivateKey = <server private key>
Address = 10.8.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <laptop public key>
AllowedIPs = 10.8.0.2/32The home router also needs a port forward, UDP 51820 to 192.168.20.5, or the handshake never starts and the client logs Handshake for peer 1 did not complete after 5 seconds, retrying. This guide assumes you are past that point.
Four lines differ from a plain full-tunnel setup, and each one is deliberate.
AllowedIPs = 10.8.0.0/24, 192.168.20.0/24on the client, in place of0.0.0.0/0, ::/0. This is a split tunnel: the tunnel subnet and the home LAN go throughwg0, and everything else keeps its local route. Your web browsing does not run through your home connection, which is usually what you want when all you need is the NAS.AllowedIPs = 10.8.0.2/32on the server, one address rather than a range. Write10.8.0.0/24there and that single client may claim any address inside the tunnel. Add a second peer with an overlapping range later and the traffic moves to whichever peer was configured last, with no error printed anywhere.PersistentKeepalive = 25on the client only. The client sits behind NAT (network address translation), and its router forgets the UDP mapping after a minute or two of silence, so the server can no longer reach it. The server has a public address and needs no keepalive.- No
DNS =line yet. Adding one changes name resolution for the whole client machine. The DNS section below explains what it does before you turn it on.
A full tunnel does reach the LAN as well, since 0.0.0.0/0 matches every address. It costs you all your traffic and a subnet collision you cannot fix from the client.
Why the same subnet at both ends breaks it
Pick a home subnet that almost nobody else uses, such as 192.168.20.0/24 or 10.44.7.0/24. 192.168.1.0/24 and 192.168.0.0/24 are the factory defaults on most consumer routers, so sooner or later your laptop sits on a café or hotel network holding exactly that range.
The collision is a hard stop, and it looks different in the two cases. With a split tunnel, wg-quick tries to add a route for a prefix that already exists on the Wi-Fi interface, ip route add refuses, and the interface fails to come up:
RTNETLINK answers: File existsWith a full tunnel, wg-quick installs policy routing rules that deliberately keep the more specific routes from the main table. The local 192.168.1.0/24 route wins over the tunnel, so every packet for the remote LAN leaves over the local link. The tunnel is up, the handshake is fine, and the NAS is unreachable. Renumbering the home LAN is the only real fix.
Turn the server into a router
ip route show default
printf 'net.ipv4.ip_forward = 1\n' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forwardThe first command gives you the real name of the LAN interface. Current images use names like enp1s0 or ens3, rarely eth0, and a masquerade rule that names the wrong interface matches nothing. The last command should print net.ipv4.ip_forward = 1. A bare sudo sysctl -w sets the same value, but it is gone after the next reboot, and that is the classic "it worked until Tuesday" report.
Forwarding also has to survive the firewall. On Ubuntu with ufw active, forwarded packets are dropped unless DEFAULT_FORWARD_POLICY="ACCEPT" is set in /etc/default/ufw. Docker sets the same policy on its own, so sudo iptables -S FORWARD | head -1 printing -P FORWARD DROP on a box you never firewalled by hand means Docker did it, and your tunnel traffic needs an explicit accept rule.
Why the replies never come back
Get rules 1 to 3 right and the ping really does reach the NAS. You still see nothing, because the reply has no way home. The NAS answers 10.8.0.2, an address outside its own subnet, so it hands the packet to its default gateway, the home router at 192.168.20.1. That router has never heard of 10.8.0.0/24, so it forwards the reply to its own default gateway, your internet connection, where it is dropped. The request arrives and the reply is thrown away.
Option A: masquerade on the WireGuard server. The server rewrites the source address of every forwarded packet to 192.168.20.5, its own LAN address. The NAS now sees a request from a neighbour on its own subnet, replies straight to the server, and the server reverses the rewrite and sends it back down the tunnel. Nothing else on the LAN has to change.
Put it in the server's [Interface] block so the rule appears and disappears with the interface:
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o enp1s0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o enp1s0 -j MASQUERADEOn a box already managed by nftables, write it into /etc/nftables.conf instead:
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" counter masquerade
}
}Keep the counter keyword. Without it, sudo nft list ruleset prints the rule with no packet count, and that count is exactly what tells you whether the rule is being used.
Masquerade brings a second benefit that is easy to miss. Plenty of hosts run a firewall that only accepts connections from their own subnet. Windows file sharing behaves this way by default, and so do several NAS admin panels. A packet from 10.8.0.2 is dropped by the target host even when your routing is perfect. After masquerade the source is a LAN address, so those rules match. The cost is that every tunnel client looks like 192.168.20.5 in the logs on every LAN host, so you cannot tell clients apart and per-client rules on LAN devices cannot work.
Option B: a static route on the home router. Tell the router that 10.8.0.0/24 lives behind 192.168.20.5. On a Linux router that is one command:
sudo ip route add 10.8.0.0/24 via 192.168.20.5Consumer routers keep a page called Static Routes or Routing under the advanced settings: destination 10.8.0.0, mask 255.255.255.0, gateway 192.168.20.5. Save it into the router's stored configuration, because an ip route add typed on a Linux box is gone at the next reboot.
This keeps the real client address, so logs and per-client rules on your LAN stay meaningful. It needs a router that supports static routes, and it only helps hosts that use that router as their default gateway. Any host with a subnet-scoped local firewall still needs its own rule for 10.8.0.0/24. Start with masquerade, since it needs nothing outside the box you already control, and move to the static route when you want real client addresses.
How to find which of the four is wrong
Work outward from the client. Each step tells you whether the packet got that far.
Does the packet enter the tunnel? On the client:
ip route get 192.168.20.10The answer should name dev wg0. If it names your Wi-Fi interface, rule 1 is wrong and the client's AllowedIPs does not cover the LAN subnet. A ping: connect: Network is unreachable points at the same line.
Do packets arrive at the server? Run this on the server, then ping the NAS from the client:
sudo tcpdump -ni wg0 icmpA working path shows IP 10.8.0.2 > 192.168.20.10: ICMP echo request, where ICMP is internet control message protocol, the thing ping uses. Nothing here, with a healthy handshake, means rule 2: the server's AllowedIPs for that peer does not include 10.8.0.2, so the packet was dropped at decryption before it ever reached wg0.
Do they leave for the LAN? On the server, watching the LAN side:
sudo tcpdump -ni enp1s0 icmpRequests visible on wg0 and nothing here means rule 3, so forwarding is off or a FORWARD rule dropped the packet. Requests here with source 10.8.0.2 and no replies means rule 4, so the reply has no route back. Requests here with source 192.168.20.5 and no replies means your masquerade rule is working and the target host itself is refusing, so look at the firewall on the NAS. The same ladder works for anything else once you swap icmp for port 445 or whichever port you care about.
I can reach the IP but not the name
ssh 192.168.20.10 works and ssh nas.home.arpa fails:
ssh: Could not resolve hostname nas.home.arpa: Name or service not knownNothing is wrong with the tunnel. Name resolution is a separate path, and your laptop is still asking the resolver it learned from the local Wi-Fi. That resolver knows nothing about the names on your home network.
Two things have to be true for home names to work. The resolver's address must sit inside AllowedIPs on the client, or the DNS (domain name system) query never enters the tunnel. And the resolver must be willing to answer a query whose source address is 10.8.0.2. Many home resolvers refuse by default: dnsmasq running with local-service answers only queries from a directly attached subnet, and Pi-hole ships with a listening mode that permits local requests only. A masquerade rule hides this problem, because after the rewrite the query arrives from 192.168.20.5.
The client side is one line:
DNS = 192.168.20.1On a Linux client that needs openresolv or an equivalent, otherwise wg-quick stops with resolvconf: command not found. Know what it does on a systemd-resolved machine before you set it: wg-quick registers those servers exclusively, so while the tunnel is up every lookup on the laptop goes to your home resolver, not only the home names. Check the result with resolvectl status wg0. If you want home names resolved at home and everything else resolved locally, that is split DNS, and fixing DNS over a WireGuard tunnel covers the configuration in full.
No public IP at home? Put a VPS in the middle
If your router's status page shows a WAN address inside 100.64.0.0/10, or a private 192.168.x.x address, you are behind CGNAT (carrier grade network address translation) and no handshake from the internet can reach your house. An outgoing handshake still works normally, so the fix is a third node with a public address. A small VPS runs the hub, and the home box connects out to it.
The four rules do not change. They now apply across two hops, so the bookkeeping doubles.
- On the VPS, the home box's peer entry gets
AllowedIPs = 10.8.0.3/32, 192.168.20.0/24: its own tunnel address, plus the subnet it is allowed to speak for. - On the VPS, the laptop's peer entry stays
AllowedIPs = 10.8.0.2/32. - On the laptop, the VPS peer gets
AllowedIPs = 10.8.0.0/24, 192.168.20.0/24, because everything now goes to the hub. - On the home box, the VPS peer gets
AllowedIPs = 10.8.0.0/24, and the home box carriesPersistentKeepalive = 25, since it is the side behind NAT now. - The VPS needs
net.ipv4.ip_forward = 1too, and its forward chain must allowwg0towg0, because laptop traffic arrives and leaves on the same interface. A firewall written for a plain full-tunnel VPS blocks exactly that.
If you have not built the VPS end yet, setting up WireGuard on a VPS covers key generation, the firewall and the systemd unit. The wider pattern of reaching a machine that cannot accept incoming connections is in opening a reverse tunnel from behind CGNAT. Once hand-managing peer addresses stops being fun, running a Tailscale subnet router does the same routing job with the address bookkeeping automated.
Make it survive a reboot
sudo systemctl enable --now wg-quick@wg0
sudo systemctl enable --now nftables
sudo wg showenable --now is the half people skip. A hand-run wg-quick up wg0 is gone after the next kernel upgrade and reboot. wg show should list the peer with a recent latest handshake line and non-zero transfer counters in both directions.
Adding a second client later does not need a restart, which would drop everyone currently connected:
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip prints the config without the keys that only wg-quick understands, and syncconf applies the difference while live sessions keep running. It updates peers only. A changed Address or a new PostUp line still needs a full down and up.
FAQ
Why can I ping the WireGuard server but nothing else on the home LAN?
Pinging 10.8.0.1 only proves the tunnel is up. The rest of the LAN is a routing problem. The client's AllowedIPs must include 192.168.20.0/24, or the packet never enters the tunnel. The server needs net.ipv4.ip_forward set to 1, or it drops anything not addressed to itself. And the LAN needs a route back to 10.8.0.0/24. Run sudo tcpdump -ni enp1s0 icmp on the server while you ping: requests leaving with source 10.8.0.2 and no replies coming back means the return path is the missing piece.
Do I need a static route on my home router?
Only if you skip the masquerade rule. A masquerade rule on the WireGuard server rewrites the source address of tunnel traffic to the server's own LAN address, so LAN hosts reply to a neighbour they already know how to reach and the router is never involved. A static route for 10.8.0.0/24 via the server's LAN address is the alternative. It is worth the trouble when you want real client addresses in the logs on your LAN hosts, or per-client firewall rules there.
Why does the same subnet at both ends break the tunnel?
Your laptop cannot hold two routes for one prefix. If the local network hands out 192.168.1.0/24 and your home LAN is also 192.168.1.0/24, a split-tunnel wg-quick up fails when ip route add refuses with RTNETLINK answers: File exists. A full tunnel comes up instead, but wg-quick installs policy rules that keep the more specific routes from the main table, so the local network wins and the remote LAN stays unreachable. Renumber the home LAN onto something rare such as 192.168.20.0/24. There is no client-side fix.
I can reach the NAS by IP but not by name. What is missing?
Name resolution does not follow the tunnel on its own. Add DNS = 192.168.20.1, your home resolver, to the client [Interface] block, and make sure that address falls inside the peer's AllowedIPs, or the query never enters the tunnel. Then check that the resolver answers queries from outside its own subnet, because dnsmasq with local-service and Pi-hole's local-only listening mode both refuse them. A masquerade rule on the WireGuard server works around that by rewriting the query's source address.
My home connection has no public IP. Can I still reach my LAN?
Yes, with a third node. Behind CGNAT your router's WAN address is private, so no peer on the internet can start a handshake with it, while an outgoing handshake works normally. Run WireGuard on a VPS with a public address, have the home box connect out to it with PersistentKeepalive = 25, and give the home box's peer entry on the VPS an AllowedIPs holding its tunnel address plus 192.168.20.0/24. The VPS then needs forwarding turned on and a forward rule that allows wg0 to wg0.