A complete, beginner-friendly tutorial for building a "home-exit" WireGuard VPN from nothing. If you follow every step in order, you will end up with a VPN where your phone and laptop — anywhere in the world — browse the internet as if they were sitting at home, with access to your home network. This version uses an always-on Windows PC as the home-exit device.
No prior WireGuard experience is assumed. Commands are copy-paste ready; placeholders in <ANGLE_BRACKETS> are the only things you change.
A normal commercial VPN sends your traffic out through the VPN company's servers. This one sends your traffic out through your own house. That gives you things a commercial VPN can't:
The problem: your home internet connection usually has no fixed public address, and your router blocks unsolicited inbound connections. So your devices can't just "dial home".
The solution: rent a cheap always-on cloud server with a public IP and use it as a meeting point (a "hub"). Everything connects out to the hub — including an always-on machine at home that keeps a permanent tunnel open. The hub then relays your phone/laptop traffic down that home tunnel and out to the internet.
YOUR DEVICES (phone, laptop) ── anywhere
│ connect out to the hub
▼
┌───────────────────────────────┐
│ HUB — cloud VM, public IP │ the meeting point (relay only)
└───────────────┬───────────────┘
│ a permanent tunnel the home PC keeps open
▼
┌───────────────────────────────┐
│ HOME PC — always-on Windows │ the internet "exit"
└───────────────┬───────────────┘
▼
your home internet + your home network
Three kinds of machine:
| Role | What it is | What it does |
|---|---|---|
| Hub | A cheap cloud VM with a public IP (e.g. Oracle Cloud free tier, a $5 VPS), running Linux | Listens for connections. Relays traffic. Never exits to the internet itself. |
| Home exit | An always-on Windows PC on your home network | Keeps a permanent tunnel to the hub. Provides the internet exit. |
| Clients | Your phone, laptop, etc. | Connect to the hub; their traffic is routed out through home. |
Trade-off: a general-purpose PC isn't purpose-built to stay on 24/7 — sleep settings, Windows Update reboots, and someone shutting it down for the night will all take the whole VPN's internet-exit offline (see Part 10). If uptime matters more to you than convenience, a small dedicated always-on box is a more robust choice for this role.
WireGuard is the tunnel technology. Key facts you need:
10.7.0.0/24 (hub = 10.7.0.1, home PC = 10.7.0.2, laptop = 10.7.0.10, phone = 10.7.0.11).AllowedIPs is the important, slightly confusing setting. It means two things at once: (a) which source IPs this peer is allowed to send, and (b) which destinations get routed to this peer. Setting a client's AllowedIPs = 0.0.0.0/0 means "route all my traffic through this tunnel" — that's what makes a full tunnel.Most tutorials make the hub the exit (it masquerades traffic straight out to the internet from the cloud). We deliberately don't. Instead:
You'll set this up in Part 5. Everything before that is the plumbing.
sudo.192.168.4.0/22 — check with ipconfig).Throughout, replace these placeholders:
| Placeholder | Meaning | Example |
|---|---|---|
<HUB_PUBLIC_IP> | The hub's public IP or DNS name | vpn.example.com |
<HOME_SUBNET> | Your home LAN in CIDR | 192.168.4.0/22 |
<HUB_WAN_IF> | The hub's internet interface | ens3 |
Find the hub's interface name with
ip -br addr— it's the one with your real IP, notlo.
On the hub (Linux):
bashsudo apt update
sudo apt install -y wireguard
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
On the Windows PC: download and install the official WireGuard for Windows client. It installs a tunnel service manager — you'll import a .conf file into it in Part 6, and it can run as a Windows service so it comes up automatically at boot, before any user logs in.
Golden rule: a private key is generated on the machine that will use it, and never copied anywhere. You only ever move public keys around.
bashumask 077
wg genkey | sudo tee /etc/wireguard/hub_private.key | wg pubkey | sudo tee /etc/wireguard/hub_public.key
The WireGuard for Windows app generates the key pair for you the moment you create a new empty tunnel (Add Tunnel → Add Empty Tunnel...) — its public key is shown right in the app window. No separate command needed.
wireguard-tools (brew install wireguard-tools on Mac) and run wg genkey | tee laptop.key | wg pubkey > laptop.pub.Now collect the public keys (safe to paste anywhere):
HUB_PUB = contents of the hub's hub_public.keyHOMEPC_PUB = shown in the WireGuard for Windows appLAPTOP_PUB, PHONE_PUB = each client's public keyKeep these handy for the next parts.
Create /etc/wireguard/wg0.conf on the hub:
bashsudo nano /etc/wireguard/wg0.conf
Paste this, filling in the placeholders:
ini[Interface]
Address = 10.7.0.1/24
ListenPort = 51820
PrivateKey = <PASTE hub_private.key CONTENTS>
MTU = 1420
# Don't let wg-quick manage routes; we do it ourselves below.
Table = off
# Allow the tunnel to forward traffic.
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT
# Reach the VPN subnet and the home LAN over the tunnel.
PostUp = ip route replace 10.7.0.0/24 dev wg0; ip route replace <HOME_SUBNET> dev wg0
# THE MAGIC: a separate routing table (200) whose default route goes INTO the tunnel,
# and rules that send client traffic (laptop .10, phone .11) into that table.
# Result: client internet traffic is relayed down to the home PC, not out the hub.
PostUp = ip route replace default dev wg0 table 200; ip route replace 10.7.0.0/24 dev wg0 table 200
PostUp = ip rule add from 10.7.0.10/32 lookup 200 priority 100; ip rule add from 10.7.0.11/32 lookup 200 priority 100
PostDown = ip rule del from 10.7.0.10/32 lookup 200 priority 100 2>/dev/null || true; ip rule del from 10.7.0.11/32 lookup 200 priority 100 2>/dev/null || true
### begin homepc ###
[Peer]
PublicKey = <HOMEPC_PUB>
# The home PC is the exit: it may send from home + act as the 0.0.0.0/0 route target.
AllowedIPs = 10.7.0.2/32, <HOME_SUBNET>, 0.0.0.0/0
### end homepc ###
### begin laptop ###
[Peer]
PublicKey = <LAPTOP_PUB>
AllowedIPs = 10.7.0.10/32
### end laptop ###
### begin phone ###
[Peer]
PublicKey = <PHONE_PUB>
AllowedIPs = 10.7.0.11/32
### end phone ###
Notice there is no MASQUERADE / NAT rule here. That's on purpose — the hub relays but never exits. The
### begin/end ###comment markers make it easy to find and swap a single peer's key later without disturbing the others.
Bring it up and make it start on boot:
bashsudo systemctl enable --now wg-quick@wg0
sudo wg show wg0 # should show the interface and your peers
Open the firewall for UDP 51820 — both in your cloud provider's security list/firewall and on the host if it runs ufw:
bashsudo ufw allow 51820/udp
In the WireGuard for Windows app, from the empty tunnel you created in Part 4, replace its contents with:
ini[Interface]
PrivateKey = <auto-filled by the app — leave it>
Address = 10.7.0.2/24
MTU = 1420
[Peer]
PublicKey = <HUB_PUB>
Endpoint = <HUB_PUBLIC_IP>:51820
# Accept the whole VPN subnet + everything (so it can be the internet exit).
AllowedIPs = 10.7.0.0/24, 0.0.0.0/0
# Dial OUT and keep the hole punched through the home router's NAT.
PersistentKeepalive = 25
PersistentKeepalive = 25is essential on the home side: your home router has no port forwarding, so the PC must keep the connection alive from the inside. The hub never initiates to the PC.
Name the tunnel something memorable (e.g. home-exit) and click Activate. In the app, tick "Enable this tunnel while running", or better — install it as a service so it survives reboots without anyone logging in (see Part 8).
Unlike a Linux box, Windows doesn't need a manual MASQUERADE iptables rule — the WireGuard for Windows client handles NAT'ing tunnel traffic onto your real network adapter automatically as part of bringing the tunnel up, as long as Internet Connection Sharing-equivalent routing is implied by the AllowedIPs above. Confirm it's working in Part 9.
Network-wide DNS ad-blocking is optional. Some home-exit setups also run a DNS sinkhole on the exit device, using its tunnel address as every client's DNS server. That's a separate, optional project — skip the
DNS = 10.7.0.2line in client configs below if you don't set that up, and clients will just use their own default DNS.
The laptop peer block already exists in the hub config from Part 5. If you're adding a new client later, add a block between markers and reload without dropping anyone:
bash# on the hub — reload live, keeping existing tunnels connected:
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'
On the laptop, make a file laptop-home.conf:
ini[Interface]
PrivateKey = <PASTE laptop.key CONTENTS>
Address = 10.7.0.10/24
MTU = 1420
[Peer]
PublicKey = <HUB_PUB>
Endpoint = <HUB_PUBLIC_IP>:51820
AllowedIPs = 0.0.0.0/0 # full tunnel: all traffic via home
PersistentKeepalive = 25
Import it:
.conf → Activate.sudo cp laptop-home.conf /etc/wireguard/ && sudo wg-quick up laptop-home.In the WireGuard mobile app, create a tunnel (it makes the key pair). Use the same [Peer] values (hub public key, endpoint, AllowedIPs = 0.0.0.0/0, keepalive 25), Address = 10.7.0.11/24. Then copy the app's public key into a new ### begin phone ### block on the hub and syncconf as in 7a.
Tip: for phones, the WireGuard app can generate a QR code on the machine that holds the config (
qrencode -t ansiutf8 < phone.conf), and you scan it with the app. That's what QR codes are for here — importing to a phone, not to a computer.
This is the part that matters most when the exit is a general-purpose PC rather than a dedicated box, since PCs sleep, reboot for updates, and get shut down.
sc query WireGuardTunnel$home-exit in an elevated PowerShell.With the client tunnel active:
bash# 1. Handshake — on the hub, the client peer shows a recent handshake + transfer:
sudo wg show wg0
# 2. Exit is HOME, not the hub. Run on the client. It MUST print your home public IP,
# and must NOT be the hub's public IP (that would mean traffic is escaping the hub):
curl -4 ifconfig.me
# 3. Home network reachable over the tunnel:
ping <A_DEVICE_ON_YOUR_HOME_LAN>
If both pass, you're done. Toggle the tunnel from the WireGuard app or wg-quick up/down as needed.
AllowedIPs = 10.7.0.0/24, <HOME_SUBNET> (VPN + home LAN only).wg0.conf on the hub, copy it (sudo cp wg0.conf wg0.conf.bak-$(date +%s)), then use wg syncconf to apply live without dropping the other peers.fail2ban and a locked-down SSH config.10.7.0.2 here) from anything else on the VPN — a second forwarding layer on top just adds complexity and attack surface for no new capability.| Machine | VPN IP | Key role |
|---|---|---|
| Hub (cloud) | 10.7.0.1 | relay + policy routing; no internet exit |
| Home PC | 10.7.0.2 | internet exit (masquerade) |
| Laptop | 10.7.0.10 | full-tunnel client, routed to home PC via table 200 |
| Phone | 10.7.0.11 | full-tunnel client, routed to home PC via table 200 |
Ports: UDP 51820 (hub listens; home PC and clients dial out to it).
Home LAN: <HOME_SUBNET>.