A personal VPN running across a phone, laptop, tablet, and home server used to mean wrestling OpenVPN configs, certificate authorities, and a mental model of TLS that didn’t really fit the use case. WireGuard collapses that to a couple of dozen lines per device, a kernel module that’s been part of Linux since version 5.6, and a Windows client that finally hit a stable 1.0 on April 18, 2026 after years as a 0.x release. The result is a VPN you can read end-to-end in an afternoon and audit yourself — provided you understand what each line does and where the tripwires are.
This guide is the configuration walkthrough that the official quickstart is too brief to be: how to lay out the topology, what AllowedIPs actually does on each side, why PersistentKeepalive matters for half your devices and not the other half, and the operational mistakes that quietly break tunnels for weeks before anyone notices. It assumes one server with a public IP — a VPS or a port-forwarded home box — and a fleet of roaming clients that need to reach each other and the internet through it.
Why WireGuard’s Design Shapes Your Configuration
WireGuard is a UDP protocol with a fixed cryptographic suite — ChaCha20-Poly1305 for symmetric encryption, Curve25519 for ECDH key exchange, BLAKE2s for hashing, HKDF for key derivation, and the Noise protocol framework for the handshake. There are no cipher negotiation knobs and no certificate chains. Every peer is identified by a Curve25519 public key, and that key is the entire identity of the device on the network.
This minimalism drives every configuration decision below. There is no concept of a “client” or “server” in the protocol — only peers. The asymmetry you see in real deployments comes from one peer having a stable public endpoint and forwarding traffic, while others sit behind NAT and only initiate connections. The protocol doesn’t care; the routing and firewall around it does.
The other design choice that affects every config you write is cryptokey routing. WireGuard maps each peer’s public key to a list of IP ranges (AllowedIPs). Outbound packets get encrypted to whichever peer owns the destination IP. Inbound packets are dropped unless their inner source IP falls within that peer’s AllowedIPs. This single field is simultaneously the routing table and the access control list. Misconfigure it and you’ll get silent failures: handshakes succeed, packets vanish.
Topology: Hub-and-Spoke Is the Default for a Reason
For a personal multi-device setup, the right topology is almost always hub-and-spoke, with the VPS acting as the hub. Every roaming device — phone, laptop, tablet — connects to the hub. Traffic between two clients (phone reaching home laptop) routes through the hub. This is not the most performant option in raw throughput, but it sidesteps three problems at once: NAT traversal between two clients (which WireGuard does not solve natively), dynamic endpoint discovery (also not solved natively), and the configuration sprawl of a full mesh, where each new device requires editing every other device’s config.
A full mesh is technically achievable and worth it for fixed peers in known network positions — a home server and a remote backup target, for instance. For a phone that hops between LTE, hotel Wi-Fi, and home, mesh is the wrong tool. Stick to hub-and-spoke and make the hub do the routing work.
[Peer] block per device.[Peer] block (the hub). NAT’d, behind firewalls, IP changes constantly.Generating Keys and Planning the Address Space
Each device needs its own keypair. Generate them on the device that will hold the private key — never copy private keys between machines.
wg genkey | tee privatekey | wg pubkey > publickeywg genkey produces a base64-encoded Curve25519 private key. Pipe it through wg pubkey to derive the matching public key. The private key file should be chmod 600 and owned by root on the hub; on mobile clients, the WireGuard app handles storage in the platform keystore.
For optional post-quantum hardening, generate a pre-shared key with wg genpsk and add it to each peer relationship. This adds a symmetric layer on top of the Curve25519 handshake, mitigating the scenario where a future quantum attacker has captured today’s traffic and breaks Curve25519 later. The cost is one shared secret per peer pair to manage.
For the address space, use a private RFC 1918 range that doesn’t overlap anything you actually use. 10.10.0.0/24 is a reasonable default — avoid 192.168.1.0/24 and 192.168.0.0/24 because they collide with most home and hotel networks. Assign 10.10.0.1 to the hub and increment from .2 for each device. Twenty-four bits is more than any personal deployment needs; the constraint is on you to track which device has which address, not on the protocol.
Hub Configuration
The hub config goes in /etc/wireguard/wg0.conf. The [Interface] section describes the hub itself. Each [Peer] block describes one device.
[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <hub-private-key>
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# phone
PublicKey = <phone-public-key>
PresharedKey = <phone-psk>
AllowedIPs = 10.10.0.2/32
[Peer]
# laptop
PublicKey = <laptop-public-key>
PresharedKey = <laptop-psk>
AllowedIPs = 10.10.0.3/32Three details on the hub side that trip people up:
AllowedIPs on the hub is per-device and narrow. Each peer entry lists only that device’s VPN IP as a /32. This is the source-address ACL — the hub will only accept packets from the phone’s tunnel that claim to come from 10.10.0.2. If you set AllowedIPs = 0.0.0.0/0 on a peer block on the hub, you’ve told the hub to route all outbound traffic to that one peer and to accept any source from it. That’s almost never what you want server-side. Reserve 0.0.0.0/0 for the client’s config, where it has the opposite meaning.
SaveConfig = true is dangerous on the hub. It causes wg-quick to overwrite the conf file on shutdown with the runtime state. If you’ve added peers manually since the last restart, fine — but if you’ve added comments, PostUp rules with shell expansions you didn’t intend to commit, or peers mid-restart, those vanish. Leave it off for a hand-managed multi-device hub.
The MASQUERADE rule turns the hub into a NAT gateway. Without it, packets reach the hub’s tunnel interface but never leave through eth0 with a valid return path. Substitute eth0 for whatever the hub’s actual public-facing interface is — ip route show default will tell you. Also enable net.ipv4.ip_forward = 1 in /etc/sysctl.conf and apply with sysctl -p, or the kernel will silently drop forwarded packets regardless of iptables.
Client Configuration
A roaming client’s config is shorter and inverts the routing logic.
[Interface]
Address = 10.10.0.2/32
PrivateKey = <phone-private-key>
DNS = 10.10.0.1
[Peer]
PublicKey = <hub-public-key>
PresharedKey = <phone-psk>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25Endpoint is the hub’s public hostname or IP. Hostnames are resolved at tunnel start and on each handshake, so a dynamic DNS provider works fine — the resolution lag on IP changes is at most a handshake interval (every 2 minutes when traffic flows).
AllowedIPs = 0.0.0.0/0, ::/0 means “send everything through the tunnel” — full-tunnel VPN behavior, which is what most personal VPNs want for privacy on hostile networks. If you only want split-tunnel access to specific resources behind the hub (a home subnet at 192.168.1.0/24, say), restrict AllowedIPs to those ranges and traffic to the public internet keeps using the local network.
PersistentKeepalive = 25 is mandatory for any client behind NAT or a stateful firewall, which is every phone, every laptop on a coffee shop network, and most home connections. NAT mappings expire after 30–120 seconds of inactivity, depending on the device. Twenty-five seconds is the documented sensible default and works against the vast majority of consumer-grade NAT timeouts. Without it, your tunnel “works” until you stop using it for a minute, then your phone sits there unable to receive anything until you trigger an outbound packet.
DNS = 10.10.0.1 routes DNS queries through the hub. If you don’t run a resolver on the hub (something like unbound or dnsmasq), point this at a public resolver you trust — 1.1.1.1, 9.9.9.9. The risk of leaving DNS at the device’s default is DNS leakage: the tunnel encrypts your traffic but your queries still go to the local Wi-Fi’s resolver, which sees every hostname you visit.
Configuration Directive Reference
/24 on hub, /32 on clients.%i expands to interface name./32 per peer on hub, 0.0.0.0/0 for full tunnel on client.wg genpsk). Post-quantum hedge.25 for NAT’d clients, omit on the hub.Bringing the Tunnel Up
On Linux, wg-quick up wg0 reads /etc/wireguard/wg0.conf, creates the interface, applies routes, and runs PostUp. To start at boot, systemctl enable wg-quick@wg0. The state can be inspected with wg show, which lists each peer, the latest handshake time, transfer counts, and current endpoint:
interface: wg0
public key: ...
listening port: 51820
peer: <phone-public-key>
endpoint: 203.0.113.42:54321
allowed ips: 10.10.0.2/32
latest handshake: 4 seconds ago
transfer: 21.11 KiB received, 38.92 KiB sentA latest handshake value older than two minutes on an active client almost always means the keepalive isn’t firing or the endpoint moved and the hub hasn’t updated it. WireGuard learns endpoint changes from authenticated incoming packets — if the client side stops sending, the hub can’t reach back.
On Android and iOS, the official WireGuard apps accept the same conf file syntax; export it as a QR code on the desktop with qrencode -t ansiutf8 < client.conf and scan from the phone. On Windows, the GUI client now at version 1.0 (released April 18, 2026) imports the same files unchanged — the long-running 0.x branch is finally retired, with the WireGuardNT driver fix landed for the MTU-notification bug Microsoft never addressed in NotifyIpInterfaceChange. macOS and iOS share a TestFlight track maintained by the same project.
Pitfalls That Burn Hours
AllowedIPs collisions on the hub. Each peer’s AllowedIPs on the hub must be unique. If two peers claim 10.10.0.0/24, the most recently configured one wins for outbound routing and the other becomes unreachable. Use /32 per device unless a peer is itself a router into a subnet.
Forgetting IP forwarding. The MASQUERADE rule alone won’t move packets — the kernel needs net.ipv4.ip_forward = 1. The most common symptom is “tunnel comes up, hub is pingable from clients, but nothing on the public internet works.”
MTU pathology. WireGuard’s default MTU is 1420 (1500 minus 80 bytes of overhead). On networks that themselves tunnel — PPPoE, some mobile carriers, double-NAT scenarios — the effective MTU is lower and large packets get black-holed. Setting MTU = 1280 in [Interface] on the affected client almost always resolves it. The symptom: small pings work, web pages load partially or hang.
The wg-portal mass-assignment CVE. If you’re running the wg-portal web UI to manage peers (h44z/wg-portal), upgrade to 2.1.3 or later. CVE-2026-27899, published February 26, 2026 with a CVSS of 8.8, lets any authenticated non-admin user send a PUT to their own profile with "IsAdmin": true and become a full administrator on next login — a textbook mass-assignment flaw. The wg-portal project is third-party tooling, not part of WireGuard itself, but it’s common enough in personal deployments that the risk is real. Don’t expose any management UI to the internet without an authenticated reverse proxy in front of it.
Key reuse across devices. Some guides suggest copying a single client conf to multiple devices “to keep things simple.” Don’t. Two devices with the same private key produce undefined behavior — handshakes race, the hub’s endpoint cache flips between them, and AllowedIPs source-address checking breaks because both devices claim the same VPN IP. One keypair per device, always.
When Hub-and-Spoke Isn’t Enough
Three scenarios push beyond a single hub:
A second region — adding a hub in another continent so a client in Asia doesn’t bounce through a US VPS — means each client gets two [Peer] blocks (one per hub), each with non-overlapping AllowedIPs (split by destination geography or by application), or you handle failover at the application layer.
Site-to-site — connecting a home network to a remote office through the VPN — requires the home-side peer to have AllowedIPs that include the home subnet (e.g., 192.168.1.0/24) on the hub side, plus IP forwarding on the home peer. The remote clients add the home subnet to their AllowedIPs for the hub peer, and the hub knows to forward packets for that subnet to the home peer.
True mesh — direct peer-to-peer connections without bouncing through the hub — needs either every device to have a stable endpoint (rare), an out-of-band signaling mechanism (which WireGuard does not provide), or a tool like Tailscale or Headscale that wraps WireGuard with a coordination server. At that point you’re no longer running plain WireGuard, and the simplicity dividend that motivated using it shrinks.
wg genpsk) for post-quantum hedging. Cheap, strict win.[Peer] block immediately. Keys are the only auth.wg-quick rewrites your file and your comments evaporate.2.1.3+ for CVE-2026-27899 if used.FAQ
Should I use TCP-over-WireGuard or just stick with UDP? WireGuard is UDP-only by protocol design. Networks that block UDP entirely — some corporate guest Wi-Fi, occasionally hotel networks — break it completely. The workaround is wrapping the UDP traffic in a TCP tunnel using udp2raw, udptunnel, or running WireGuard inside an SSH tunnel. This adds latency and a moving part, but it’s the practical answer for hostile networks.
Does WireGuard hide the fact that I’m using a VPN? No. WireGuard’s UDP packets have a recognizable signature, and an active probe will identify a ListenPort running WireGuard within seconds. Deep packet inspection at country-firewall scale will block it. Obfuscation needs an additional layer like wireguard-obfs or routing through a tool that masquerades the traffic as something else.
How does WireGuard compare to OpenVPN for a personal VPN today? WireGuard wins on throughput (often 2–4× higher), code size (the kernel module is roughly 4,000 lines vs OpenVPN’s hundreds of thousands), and configuration simplicity. OpenVPN wins on TCP support, broad firewall traversal, and per-user authentication via username/password rather than per-device keys. For a personal device fleet, WireGuard’s tradeoffs are correct. For an organization with hundreds of users rotating through, the auth model gets harder.
Is the lack of dynamic IP support a real problem in practice? Less than expected. WireGuard learns a peer’s current endpoint from authenticated handshakes; if your home connection’s IP changes, the next packet your home peer sends to the hub updates the hub’s endpoint cache. The pain only appears when both peers have dynamic IPs and neither can initiate (the classic double-NAT case), which is exactly when you need a hub anyway.
The Verdict on a Personal Multi-Device Setup
WireGuard’s appeal isn’t that it’s the most feature-rich VPN — it’s that you can hold the entire mental model in your head while writing a config. Five devices, fifty lines of conf, one UDP port on a VPS, and the same setup works identically on Linux, Android, iOS, macOS, and now a stable Windows. The footguns are concentrated in three places: AllowedIPs semantics flipping between hub and client, NAT keepalive being non-default, and IP forwarding being a sysctl rather than something the conf file handles.
Get those three right and the rest of the configuration is mechanical. Skip them and you’ll spend an evening reading wg show output trying to understand why packets handshake but don’t flow. Either outcome is much faster than building the equivalent with certificates and a PKI.






