Skip to main content
โšก Calmops

WireGuard VPN Use Cases: Remote Access, Site-to-Site, and Kubernetes

Introduction

WireGuard’s simplicity makes it adaptable to many scenarios. This guide shows concrete configurations for the most common use cases โ€” not theory, but working configs you can adapt.

Use Case 1: Remote Access VPN

The classic use case: employees connecting to the office network from home.

Architecture

Employee laptop (10.0.0.2) โ”€โ”€WireGuardโ”€โ”€โ†’ VPN Server (10.0.0.1) โ”€โ”€โ†’ Office Network (192.168.1.0/24)

Server Config

# /etc/wireguard/wg0.conf โ€” VPN server
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <server_private_key>

# Route traffic to office network
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# Alice's laptop
PublicKey = <alice_public_key>
AllowedIPs = 10.0.0.2/32

[Peer]
# Bob's laptop
PublicKey = <bob_public_key>
AllowedIPs = 10.0.0.3/32

Client Config (Split Tunnel โ€” only office traffic through VPN)

# Alice's laptop โ€” only routes office network through VPN
[Interface]
Address = 10.0.0.2/24
PrivateKey = <alice_private_key>
DNS = 192.168.1.1  # office DNS server

[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.company.com:51820
# Only route office network through VPN (split tunnel)
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25

Client Config (Full Tunnel โ€” all traffic through VPN)

[Interface]
Address = 10.0.0.2/24
PrivateKey = <alice_private_key>
DNS = 1.1.1.1

[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.company.com:51820
# Route ALL traffic through VPN
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Use Case 2: Site-to-Site VPN

Connect two office networks so they can communicate directly.

Architecture

Office A (192.168.1.0/24) โ”€โ”€WireGuardโ”€โ”€โ†’ Office B (192.168.2.0/24)
VPN IP: 10.0.0.1                          VPN IP: 10.0.0.2

Office A Config

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <office_a_private_key>

# Route Office B's subnet through the tunnel
PostUp = ip route add 192.168.2.0/24 via 10.0.0.2 dev wg0
PostDown = ip route del 192.168.2.0/24

[Peer]
PublicKey = <office_b_public_key>
Endpoint = office-b.example.com:51820
# Office B's VPN IP + Office B's LAN
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
PersistentKeepalive = 25

Office B Config

[Interface]
Address = 10.0.0.2/24
ListenPort = 51820
PrivateKey = <office_b_private_key>

PostUp = ip route add 192.168.1.0/24 via 10.0.0.1 dev wg0
PostDown = ip route del 192.168.1.0/24

[Peer]
PublicKey = <office_a_public_key>
Endpoint = office-a.example.com:51820
AllowedIPs = 10.0.0.1/32, 192.168.1.0/24
PersistentKeepalive = 25

Test Connectivity

# From Office A, ping a machine in Office B
ping 192.168.2.100

# Check routing
ip route show | grep 192.168.2
# 192.168.2.0/24 via 10.0.0.2 dev wg0

Use Case 3: Hub-and-Spoke (Multiple Offices)

One central hub, multiple spoke offices. Spokes communicate through the hub.

Spoke A (10.0.0.2) โ”€โ”€โ†’ Hub (10.0.0.1) โ†โ”€โ”€ Spoke B (10.0.0.3)
                              โ†‘
                        Spoke C (10.0.0.4)

Hub Config

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <hub_private_key>

# Enable routing between spokes
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT

[Peer]
# Spoke A
PublicKey = <spoke_a_public_key>
AllowedIPs = 10.0.0.2/32, 192.168.10.0/24  # Spoke A's LAN

[Peer]
# Spoke B
PublicKey = <spoke_b_public_key>
AllowedIPs = 10.0.0.3/32, 192.168.20.0/24  # Spoke B's LAN

[Peer]
# Spoke C
PublicKey = <spoke_c_public_key>
AllowedIPs = 10.0.0.4/32, 192.168.30.0/24  # Spoke C's LAN

Spoke Config (same pattern for all spokes)

[Interface]
Address = 10.0.0.2/24
PrivateKey = <spoke_a_private_key>

[Peer]
PublicKey = <hub_public_key>
Endpoint = hub.example.com:51820
# Route all other spokes' traffic through hub
AllowedIPs = 10.0.0.0/24, 192.168.20.0/24, 192.168.30.0/24
PersistentKeepalive = 25

Use Case 4: Kubernetes Pod Networking

WireGuard can encrypt pod-to-pod traffic across nodes (used by Calico, Cilium, Flannel):

# Cilium with WireGuard encryption
helm install cilium cilium/cilium \
    --set encryption.enabled=true \
    --set encryption.type=wireguard \
    --set encryption.nodeEncryption=true

# Verify WireGuard is active
kubectl exec -n kube-system cilium-xxx -- cilium status | grep Encryption
# Encryption: WireGuard (enabled)

# Check WireGuard interfaces on nodes
kubectl debug node/worker-1 -it --image=busybox -- wg show

Manual Kubernetes Node-to-Node Encryption

# On each Kubernetes node, create a WireGuard interface
# Node 1 (10.0.0.1) โ†โ†’ Node 2 (10.0.0.2)

# Node 1
wg genkey | tee /etc/wireguard/k8s.key | wg pubkey > /etc/wireguard/k8s.pub

cat > /etc/wireguard/k8s.conf << EOF
[Interface]
Address = 10.100.0.1/24
PrivateKey = $(cat /etc/wireguard/k8s.key)
ListenPort = 51821

[Peer]
PublicKey = <node2_public_key>
Endpoint = 192.168.1.2:51821  # Node 2's real IP
AllowedIPs = 10.100.0.2/32, 10.244.2.0/24  # Node 2's pod CIDR
EOF

wg-quick up k8s

Use Case 5: IoT Device Management

Securely manage thousands of IoT devices without exposing them to the internet.

Architecture

IoT Device (Raspberry Pi) โ”€โ”€WireGuardโ”€โ”€โ†’ Management Server
                                              โ†“
                                         SSH, monitoring, updates

IoT Device Config (minimal)

# /etc/wireguard/mgmt.conf โ€” on each IoT device
[Interface]
Address = 10.10.0.{DEVICE_ID}/24
PrivateKey = <device_private_key>

[Peer]
PublicKey = <server_public_key>
Endpoint = mgmt.example.com:51820
# Only route management server traffic through VPN
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 30  # keep alive through NAT

Automated Device Provisioning

#!/bin/bash
# provision-device.sh โ€” run during device manufacturing/setup

DEVICE_ID=$1
SERVER_PUBLIC_KEY=$(cat /etc/wireguard/server_public.key)
SERVER_ENDPOINT="mgmt.example.com:51820"
VPN_IP="10.10.0.${DEVICE_ID}"

# Generate device keys
DEVICE_PRIVATE=$(wg genkey)
DEVICE_PUBLIC=$(echo "$DEVICE_PRIVATE" | wg pubkey)

# Register device on server
ssh mgmt-server "sudo wg set wg0 peer $DEVICE_PUBLIC allowed-ips ${VPN_IP}/32"
ssh mgmt-server "echo '[Peer]
PublicKey = $DEVICE_PUBLIC
AllowedIPs = ${VPN_IP}/32
# Device ID: $DEVICE_ID' | sudo tee -a /etc/wireguard/wg0.conf"

# Write config to device
cat > /etc/wireguard/mgmt.conf << EOF
[Interface]
Address = ${VPN_IP}/24
PrivateKey = $DEVICE_PRIVATE

[Peer]
PublicKey = $SERVER_PUBLIC_KEY
Endpoint = $SERVER_ENDPOINT
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 30
EOF

systemctl enable --now wg-quick@mgmt
echo "Device $DEVICE_ID provisioned with VPN IP $VPN_IP"

Use Case 6: Zero-Trust Access with wg-easy

wg-easy provides a web UI for managing WireGuard peers:

# docker-compose.yml
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    container_name: wg-easy
    environment:
      - WG_HOST=vpn.example.com
      - PASSWORD_HASH=$$2y$$10$$...  # bcrypt hash of admin password
      - WG_DEFAULT_DNS=1.1.1.1
      - WG_DEFAULT_ADDRESS=10.8.0.x
      - WG_ALLOWED_IPS=10.8.0.0/24, 192.168.1.0/24
    volumes:
      - ./wg-easy:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"  # web UI
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
    restart: unless-stopped
docker compose up -d
# Web UI: http://your-server:51821
# Create clients, download configs, generate QR codes

Use Case 7: Bypass Restrictive Firewalls

Some networks block UDP. Use WireGuard over TCP with udp2raw:

# Server: wrap WireGuard UDP in TCP
udp2raw -s -l 0.0.0.0:443 -r 127.0.0.1:51820 -k "password" --raw-mode faketcp

# Client: unwrap TCP back to UDP
udp2raw -c -l 127.0.0.1:51820 -r server.example.com:443 -k "password" --raw-mode faketcp

# Client WireGuard config: point to local udp2raw
[Peer]
Endpoint = 127.0.0.1:51820  # local udp2raw port

Monitoring All Use Cases

# Universal monitoring commands
sudo wg show                    # all interfaces
sudo wg show wg0 transfer       # bytes sent/received per peer
sudo wg show wg0 latest-handshakes  # last handshake time per peer

# Alert if peer hasn't handshaked in 3 minutes (likely disconnected)
sudo wg show wg0 latest-handshakes | while read peer timestamp; do
    age=$(($(date +%s) - timestamp))
    if [ $age -gt 180 ]; then
        echo "WARNING: Peer $peer last handshake ${age}s ago"
    fi
done

# Prometheus metrics via wireguard_exporter
docker run -d -p 9586:9586 \
    -v /etc/wireguard:/etc/wireguard:ro \
    --cap-add NET_ADMIN \
    mindflavor/prometheus-wireguard-exporter

Resources

Comments