DIY Debian Router - Part 8: WireGuard VPN for secure remote access
Introduction
This is Part 8 of the DIY Debian Router series. See Part 1 for the series introduction and links to all parts.
WireGuard is a modern VPN protocol offering simplicity, high performance, and strong cryptography. Deploying WireGuard on our router will allow secure remote access to the entire LAN without exposing individual services via port forwarding.
We'll cover:
- WireGuard installation and key generation
- Server configuration on the router
- Client configuration for mobile and desktop
- Firewall integration with nftables
- IPv4 and IPv6 routing over VPN
- Performance optimization and troubleshooting
WireGuard vs. traditional VPNs
Comparison against legacy VPN protocols:
| Feature | WireGuard | OpenVPN | IPsec |
|---|---|---|---|
| Lines of code | ~4,000 | ~100,000 | ~400,000 |
| Cryptography | Modern (Curve25519, ChaCha20) | Configurable (often outdated defaults) | Complex, legacy options |
| Performance | Excellent (kernel-level) | Good (userspace) | Good (kernel, complex) |
| Configuration | Simple (INI-style) | Complex (OpenSSL-based) | A mess |
| Key management | Static public keys | PKI certificates | IKE/PSK |
| Roaming | Seamless (silent handshake) | Requires reconnect | Requires reconnect |
WireGuard advantages:
- Simplicity: Configuration is human-readable, minimal options
- Performance: Faster than OpenVPN in typical scenarios
- Security: Small codebase reduces attack surface; modern cryptography by default
Limitations:
- Static addressing: No dynamic IP assignment (clients use fixed VPN IPs)
- Key distribution: Manual key exchange (no PKI like OpenVPN)
Security model and threat assumptions
WireGuard provides:
- Confidentiality: All traffic encrypted with ChaCha20-Poly1305
- Authenticity: Cryptographic verification via Curve25519 public keys
- Forward secrecy: Session keys rotated every 2 minutes
- Replay protection: Nonce-based anti-replay
Threat model assumptions:
- Attacker controls the network path between client and server (Internet)
- Attacker can intercept, modify, or replay packets
- Attacker does not have physical access to devices (private keys are secure)
- Client devices may be compromised (principle of least privilege applies)
Not protected against:
- Malware on client devices (VPN access = LAN access)
- Compromised router (game over for all security)
- Traffic analysis (connection timing, packet sizes)
See the WireGuard whitepaper for more details.
Key generation
WireGuard uses asymmetric cryptography (Curve25519). Each peer (server and clients) has a private key and corresponding public key.
Generate server private key:
| |
Security note: Private keys must be protected (mode 600). The umask 077 ensures this.
View keys:
Generate keys for each client device (repeat for laptop, phone, etc.):
| |
| |
Key distribution:
- Client private keys are transferred to client devices (via secure channel: USB, encrypted email, password manager)
- Client public keys remain on server (added to configuration)
- Server public key is shared with all clients
You also have the option to create an optional pre-shared key (PSK) for each client. They add an additional layer of symmetric encryption, providing post-quantum resistance:
Server configuration
/etc/wireguard/wg0.conf
[Interface]
SERVER_PRIVATE_KEY_HERE
51820
[Peer]
CLIENT1_PUBLIC_KEY_HERE
[Peer]
CLIENT2_PUBLIC_KEY_HERE
25
Replace SERVER_PRIVATE_KEY_HERE with the contents of /etc/wireguard/server_private.key. The VPN subnet (10.100.0.0/24) must not overlap with the LAN subnet. Each client requires a [Peer] section containing its public key and a unique /32 IP from the VPN subnet. Mobile clients and devices behind NAT should include PersistentKeepalive = 25 to maintain connections across network changes.
Firewall integration
WireGuard requires three firewall modifications:
- Variable definitions: Define WireGuard specific variables
- Input chain: Permit UDP traffic on port 51820 (WireGuard handshake)
- Forward chain: Permit traffic between VPN and LAN
- NAT postrouting: Masquerade VPN traffic to LAN (critical for routing)
Edit /etc/nftables.conf (configured in Part 6):
Step 1: Define WireGuard variables, add to configuration variables section:
Step 2: Add input chain rules, locate the input chain and add before the final WAN drop rule:
Step 3: Add forward chain rules after the established/related rule:
Step 4: Add NAT postrouting rule for VPN-to-Internet traffic, locate the table ip nat section and add to the postrouting chain:
Apply changes:
Verify rules:
|
|
|
DNS configuration for VPN clients
VPN clients need DNS resolution to work properly. The client configuration specifies DNS = 192.168.0.1, but by default Unbound (configured in Part 3) only allows clients on the LAN subnet, not the WireGuard subnet.
Edit /etc/unbound/unbound.conf and add the WireGuard VPN subnet to the ACL:
server:
# ... existing config ...
# Access control
# ... other rules ...
access-control: 10.100.0.0/24 allow
# ... deny rule below ...
Restart Unbound:
Enabling and starting WireGuard
Enable and start WireGuard interface at boot:
Verify interface status:
Check interface address:
Client configuration
Linux/macOS desktop client
Install WireGuard:
# Debian/Ubuntu:
# macOS (Homebrew):
Create /etc/wireguard/wg0.conf on the client:
[Interface]
CLIENT_PRIVATE_KEY_HERE
DNS =
[Peer]
SERVER_PUBLIC_KEY_HERE
CLIENT_PSK_HERE
vpn.absurdum.ca:51820
,
25
Configuration notes:
- PrivateKey: Client's private key (generated earlier)
- Address: VPN IP for this client (must match server's
AllowedIPsfor this peer) - DNS: Router's LAN IP for DNS resolution (optional, allows resolving internal hostnames)
- Endpoint: Router's public IP or DDNS hostname + port
- AllowedIPs:
0.0.0.0/0= route all traffic through VPN (full tunnel)192.168.0.0/24= route only LAN traffic through VPN (split tunnel, Internet traffic uses local connection)
Recommendation: Use split tunnel (192.168.0.0/24) for better performance. Use full tunnel (0.0.0.0/0) when on untrusted networks.
Connect:
Verify connection:
Should get replies from both addresses.
Disconnect:
Mobile clients (iOS/Android)
Install WireGuard app:
- iOS: WireGuard on App Store
- Android: WireGuard on Google Play
Configuration via QR Code
Generate configuration and QR code:
Setup on mobile:
- Open WireGuard app
- Tap "Add tunnel" → "Create from QR code"
- Scan QR code displayed in terminal
- Name the tunnel (e.g., "Home VPN")
- Toggle on to connect
Manual configuration on mobile
Alternatively, manually enter configuration in the app:
- Create new tunnel
- Enter interface settings (private key, address, DNS)
- Add peer (server public key, endpoint, allowed IPs)
Testing and verification
After you've connected a client, check peer status on the server:
Expected output includes:
peer: [CLIENT1 PUBLIC KEY]
endpoint: [CLIENT PUBLIC IP]:random_port
allowed ips: 10.100.0.2/32
latest handshake: 10 seconds ago
transfer: 5.2 KiB received, 8.1 KiB sent
You should see the client's public IP, a recent latest handshake, and non-zero values for transfers.
To test general connectivity from a client:
# Ping router's VPN IP
# Ping router's LAN IP
# Ping LAN device
# Test DNS resolution (if DNS configured)
All tests should succeed.
You can also do some basic monitoring on the server:
Looking for transfer counters to increase during client activity.
Or do a packet capture on the VPN interface:
You should see the decrypted traffic generated by your clients.
IPv6 over WireGuard
To route IPv6 traffic through VPN, assign IPv6 addresses to VPN interface.
Server configuration
Add IPv6 address to wg0.conf:
[Interface]
, fd00:100::1/64
Assign IPv6 to clients:
[Peer]
, fd00:100::2/128
Client configuration
[Interface]
, fd00:100::2/64
[Peer]
, , fd09:dead:beef::/48, fd00:100::/64
Test IPv6 connectivity:
Troubleshooting common issues
No handshake occurring
Symptom: wg show shows no latest handshake or endpoint.
Causes:
-
Firewall blocking UDP 51820:
# On server: |If no rule exists, add it.
-
Incorrect endpoint:
- Verify client's
Endpointmatches server's public IP/DDNS hostname - Test with
telnet vpn.absurdum.ca 51820(won't connect, but verifies port is reachable)
- Verify client's
-
NAT/firewall on client side:
- Some networks block outbound UDP. Test from different network (mobile data).
-
Incorrect keys:
- Verify client's
PublicKeyin server config matchesclient_public.key - Verify server's
PublicKeyin client config matchesserver_public.key
- Verify client's
Handshake succeeds but no traffic
Symptom: Handshake shown, but ping fails.
Causes:
-
IP forwarding disabled:
Should return
1. -
Missing forward chain rules:
|Add rules permitting VPN ↔ LAN traffic.
-
AllowedIPs mismatch:
- Client's
AllowedIPsmust include destination subnet (e.g.,192.168.0.0/24) - Server's
AllowedIPsfor peer must include client's VPN IP (e.g.,10.100.0.2/32)
- Client's
DNS resolution failing
Symptom: VPN connected, ping 192.168.0.1 works, but nslookup google.com fails.
Diagnostic steps:
-
Verify Unbound permits VPN subnet:
Should show
access-control: 10.100.0.0/24 allow. If missing, add per DNS configuration section. -
Check client configuration: Verify
DNS = 192.168.0.1exists in client'swg0.conf. -
Test DNS directly:
If this works but normal lookups fail, client is ignoring VPN DNS. Check system DNS settings with
resolvectl status(Linux) orscutil --dns(macOS). -
Verify Unbound is listening:
|Should show
0.0.0.0:53or specific interface bindings including VPN subnet.
Security best practices
Key management
- Never reuse keys: Each client should have unique private key
- Rotate keys periodically: Generate new keys annually or after device compromise
- Secure key distribution: Transfer keys via private/encrypted channels (password managers, encrypted USB)
Access control
Limit VPN access to specific LAN resources using firewall rules:
# Allow VPN clients to access only specific server:
This permits VPN clients to reach only 192.168.0.10, blocking rest of LAN.
Next Steps
With WireGuard working, we have something that allows secure remote access to our entire LAN. However, accessing this from the Internet requires either a static public IP or Dynamic DNS, see Part 10 for DDNS configuration to track changing residential IP addresses.