WireGuard Kernel-VPN & Cryptokey Routing: Point-to-Site & Site-to-Site Topologies
Created: 2026-11-08 · Last updated: 2026-11-08 · Nejat Hakan
Executive Summary: Legacy VPN technologies like OpenVPN and IPsec suffer from excessive code complexity, slow user-space context switches, and cumbersome cipher negotiation handshakes. Merged directly into the mainline Linux kernel (
wireguard.ko), WireGuard revolutionizes encrypted tunneling. Operating under 4,000 lines of auditable C code, it combines modern cryptography (Noise Protocol, Curve25519, ChaCha20-Poly1305) with an elegant architectural principle: Cryptokey Routing.
-
Kernel-Space Architecture
Understand why
wireguard.koachieves multi-gigabit line-rate throughput without the user-to-kernel context-switching overhead of TUN/TAP daemons. -
Cryptokey Routing
Master the dual function of
AllowedIPs: serving simultaneously as an internal kernel routing table and an ingress packet filter. -
Point-to-Site (Road Warrior)
Deploy remote administrative access with split-tunnel routing for homelab and enterprise LANs, including automated
nftablesmasquerading. -
Site-to-Site Mesh
Interconnect two distant branch office networks transparently across the public internet without double-NAT or proprietary appliances.
1. The WireGuard Architecture: Simplicity & Stealth
Unlike OpenVPN (which runs in user-space and passes every packet through the /dev/net/tun interface) or IPsec (which involves massive XFRM state engines and IKE daemons), WireGuard lives entirely inside the Linux kernel networking subsystem:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
subgraph LEG["Legacy User-Space VPN (OpenVPN)"]
A1["Kernel Network Stack"] <-->|"Context Switch (TUN Device)"| B1["User-Space OpenVPN Daemon<br/>(TLS Handshake, OpenSSL)"]
end
subgraph WG["Modern In-Kernel VPN (WireGuard)"]
A2["Kernel Network Stack"] <-->|"Zero-Copy Direct Processing"| B2["wireguard.ko (Kernel Subsystem)<br/>Noise Protocol / ChaCha20-Poly1305"]
end
Cryptographic Foundation
WireGuard deliberately eliminates cipher suites and negotiation to prevent downgrade attacks: * Key Exchange: Noise Protocol Framework using Curve25519 (Diffie-Hellman). * Encryption & Integrity: ChaCha20 for symmetric encryption paired with Poly1305 for MAC authentication (AEAD). * Hashing: BLAKE2s for hashing and HKDF. * Stealth Operation: WireGuard never responds to unauthenticated packets. Port scanners find closed UDP ports; the server is completely invisible until a cryptographically valid handshake initiates.
2. The Core Mental Model: Cryptokey Routing
In traditional networking, routing tables (ip route) and access control lists (iptables) are maintained separately. WireGuard merges them into a single construct: Cryptokey Routing.
Every peer is identified solely by its Public Key, associated with a list of AllowedIPs:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
subgraph EGRESS["Outbound Packet (Egress Routing)"]
E1["Kernel sends packet to 10.200.0.2"] --> E2{"Match destination IP against AllowedIPs list"}
E2 -->|"Match found: Peer B"| E3["Encrypt payload with Peer B Public Key.<br/>Wrap in UDP and transmit to Endpoint."]
end
subgraph INGRESS["Inbound Packet (Ingress Filtering)"]
I1["Inbound UDP packet received from Peer B"] --> I2["Decrypt payload with Peer B Public Key"]
I2 --> I3{"Does inner Source IP match Peer B AllowedIPs?"}
I3 -->|"YES"| I4["Accept packet into kernel network stack"]
I3 -->|"NO"| I5["Drop packet silently (Anti-Spoofing)"]
end
AllowedIPs Does Two Things At Once
- For Outbound Traffic: It tells the kernel: "Any packet destined for these IP ranges must be encrypted and sent to this peer."
- For Inbound Traffic: It acts as a firewall rule: "Only accept decrypted packets from this peer if their inner source IP matches this list."
3. Cryptographic Key Generation
WireGuard keys are base64-encoded 32-byte strings. Generate private and public keys using the wg utility:
# Generate server private key and derive corresponding public key
wg genkey | tee server_private.key | wg pubkey > server_public.key
# Generate client private key and derive public key
wg genkey | tee client_private.key | wg pubkey > client_public.key
# Optional: Generate a Pre-Shared Key (PSK) for post-quantum resistance
wg genpsk > preshared.key
# Protect private keys
chmod 600 *.key
4. Scenario 1: Point-to-Site (Remote Road Warrior Access)
In this scenario, a remote laptop (Road Warrior) connects over the internet to a central Debian gateway to access internal servers (192.168.1.0/24).
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
LAPTOP["Remote Laptop (Road Warrior)<br/>VPN IP: 10.200.0.2/24"] -->|"Encrypted UDP 51820"| GW["Debian VPN Gateway<br/>WAN: 203.0.113.10<br/>VPN IP: 10.200.0.1/24"]
GW -->|"Routed & Masqueraded"| LAN["Private Internal LAN<br/>192.168.1.0/24<br/>(Fileservers, Databases, VMs)"]
Server Configuration (/etc/wireguard/wg0.conf)
Enable kernel forwarding first:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/60-wireguard-forward.conf
sudo sysctl --system
Write /etc/wireguard/wg0.conf:
# File: /etc/wireguard/wg0.conf (Server)
[Interface]
# Gateway address inside the VPN subnet
Address = 10.200.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# Enable NAT masquerading when the tunnel starts, tear down on stop
PostUp = nft add table ip wg_nat; nft add chain ip wg_nat post { type nat hook postrouting priority 100 \; }; nft add rule ip wg_nat post oifname "eth0" masquerade
PostDown = nft delete table ip wg_nat
# Client 1: Laptop (Road Warrior)
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
PresharedKey = <PRESHARED_KEY>
AllowedIPs = 10.200.0.2/32
Client Configuration (/etc/wireguard/wg0.conf)
# File: /etc/wireguard/wg0.conf (Client Laptop)
[Interface]
Address = 10.200.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 192.168.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
PresharedKey = <PRESHARED_KEY>
Endpoint = 203.0.113.10:51820
# Split-Tunnel: Only route VPN and LAN traffic through the tunnel
AllowedIPs = 10.200.0.0/24, 192.168.1.0/24
# Keep stateful NAT mappings alive behind firewalls
PersistentKeepalive = 25
(To route all internet traffic through the VPN, replace AllowedIPs with 0.0.0.0/0, ::/0).
5. Scenario 2: Site-to-Site Network Interconnect
Connect two geographically separated networks (Site A: 192.168.10.0/24 and Site B: 192.168.20.0/24) without setting up complex IPsec policies:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
LANA["Site A Subnet<br/>192.168.10.0/24"] <--> GWA["Site A Gateway<br/>VPN: 10.200.0.1/30"]
GWA <-->|"Encrypted UDP Tunnel"| GWB["Site B Gateway<br/>VPN: 10.200.0.2/30"]
GWB <--> LANB["Site B Subnet<br/>192.168.20.0/24"]
Site A Gateway Configuration
# File: /etc/wireguard/wg-site2site.conf (Site A)
[Interface]
Address = 10.200.0.1/30
ListenPort = 51820
PrivateKey = <SITE_A_PRIVATE_KEY>
[Peer]
PublicKey = <SITE_B_PUBLIC_KEY>
Endpoint = site-b.example.com:51820
# Route Site B's local subnet and its tunnel IP through this peer
AllowedIPs = 10.200.0.2/32, 192.168.20.0/24
PersistentKeepalive = 25
Site B Gateway Configuration
# File: /etc/wireguard/wg-site2site.conf (Site B)
[Interface]
Address = 10.200.0.2/30
ListenPort = 51820
PrivateKey = <SITE_B_PRIVATE_KEY>
[Peer]
PublicKey = <SITE_A_PUBLIC_KEY>
Endpoint = site-a.example.com:51820
# Route Site A's local subnet and its tunnel IP through this peer
AllowedIPs = 10.200.0.1/32, 192.168.10.0/24
PersistentKeepalive = 25
6. Overcoming NAT & Optimizing MTU
1. NAT Traversal & Carrier-Grade NAT (CGNAT)
If a client or branch office sits behind an ISP utilizing Carrier-Grade NAT (100.64.0.0/10) or stateful firewalls, the firewall terminates idle UDP connection states after 30 seconds.
Adding PersistentKeepalive = 25 instructs WireGuard to transmit an authenticated empty packet every 25 seconds, keeping the stateful firewall mapping permanently open.
2. MTU Sizing: Avoiding Fragmentation & Blackholes
Standard physical Ethernet uses an MTU of 1500 bytes. WireGuard wraps original packets inside an outer IPv4/IPv6 and UDP header:
Standard Ethernet MTU: 1500 bytes
- Outer IPv4 Header: - 20 bytes (or 40 bytes for IPv6)
- Outer UDP Header: - 8 bytes
- WireGuard Encapsulation Header: - 32 bytes
- Cryptographic Auth Tag: - 16 bytes
------------------------------------------------------------------------
Optimal WireGuard Tunnel MTU: 1420 bytes (or 1412 for IPv6 WAN)
By default, wg-quick automatically calculates the tunnel MTU. If you encounter issues where web pages stall during TLS handshakes, enforce MTU clamp via nftables:
# File: /etc/nftables.conf (Add to forward chain)
chain forward {
type filter hook forward priority filter; policy drop;
# Clamp TCP MSS to Path MTU to prevent oversized packets
tcp flags syn tcp option maxseg size set rt mtu
}
7. Service Management & Live Monitoring
Start and enable the tunnel as a native systemd unit:
# Start tunnel using wg-quick
sudo wg-quick up wg0
# Enable automated start at boot
sudo systemctl enable --now wg-quick@wg0
Monitoring Active Peer Status with wg
Output:
interface: wg0
public key: jX9+B8...
private key: (hidden)
listening port: 51820
peer: 7kL2...
endpoint: 198.51.100.42:54210
allowed ips: 10.200.0.2/32
latest handshake: 1 minute, 14 seconds ago
transfer: 14.20 MiB received, 82.41 MiB sent
Verifying WireGuard Health
Look at latest handshake. If the latest handshake is under 3 minutes ago, the tunnel is alive and cryptographically authenticated. If missing, verify public keys, endpoints, and firewall port 51820/UDP.
8. Next Steps
With encrypted, high-speed WireGuard tunnels operating in kernel space, master the ultimate network diagnostic tool for deep wire-level inspection:
-
Wire-Level Packet Analysis with tcpdump & BPF
Capture raw packets, construct Berkeley Packet Filters (BPF), analyze TCP flags, and export pcap captures for Wireshark.