WireGuard has fundamentally changed VPN technology. Where OpenVPN requires hundreds of lines of configuration, complex certificate management, and careful cipher selection, WireGuard achieves the same result with a few dozen lines of configuration and a single, modern cryptographic protocol. It is fast, simple, auditable (about 4,000 lines of code in the Linux kernel module), and has been part of the mainline Linux kernel since version 5.6.

For self-hosted infrastructure, WireGuard is the best way to securely access your homelab services from anywhere. This guide covers everything from basic single-client setup to multi-site architectures.

WireGuard vs. OpenVPN

Feature WireGuard OpenVPN
Code complexity ~4,000 lines ~100,000 lines
Protocol UDP only UDP or TCP
Encryption ChaCha20, Curve25519, BLAKE2s Configurable (many options)
Performance Near wire speed (kernel module) Good (userspace)
Connection establishment ~100ms ~5-10 seconds
Configuration ~15-20 lines ~50-100 lines
Roaming support Built-in (seamless IP changes) Reconnection required
Stealth / obfuscation Limited Better (TCP mode, stunnel)

When to choose OpenVPN: If you need TCP transport (to bypass restrictive firewalls), traffic obfuscation, or compatibility with very old devices, OpenVPN remains relevant. For everything else, WireGuard is the better choice.

Installation

# Debian/Ubuntu
sudo apt install -y wireguard

# Fedora/RHEL
sudo dnf install -y wireguard-tools

# Arch Linux
sudo pacman -S wireguard-tools

# Verify kernel module is available
sudo modprobe wireguard
lsmod | grep wireguard

Key Generation

WireGuard uses Curve25519 key pairs. Each peer (server and every client) needs its own key pair:

# Generate server key pair
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key

# Generate client key pair
wg genkey | tee client1_private.key | wg pubkey > client1_public.key

# Generate a preshared key for additional security (optional, quantum-resistant)
wg genpsk > client1_preshared.key

# View keys
cat /etc/wireguard/server_public.key
cat client1_public.key

Server Configuration

# /etc/wireguard/wg0.conf (Server)
[Interface]
# Server's private key
PrivateKey = SERVER_PRIVATE_KEY_HERE
# VPN subnet address for the server
Address = 10.0.0.1/24
# UDP port to listen on
ListenPort = 51820
# Enable IP forwarding and NAT when the interface comes up
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Save configuration changes made at runtime
SaveConfig = false

# Client 1: Laptop
[Peer]
PublicKey = CLIENT1_PUBLIC_KEY_HERE
PresharedKey = CLIENT1_PRESHARED_KEY_HERE
# IP address assigned to this client
AllowedIPs = 10.0.0.2/32

# Client 2: Phone
[Peer]
PublicKey = CLIENT2_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.3/32

# Client 3: Remote server (site-to-site)
[Peer]
PublicKey = CLIENT3_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.4/32, 192.168.2.0/24
Endpoint = remote-server.example.com:51820
PersistentKeepalive = 25
# Enable IP forwarding (required for routing traffic through the VPN)
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-wireguard.conf
echo "net.ipv6.conf.all.forwarding = 1" | sudo tee -a /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf

# Start WireGuard
sudo wg-quick up wg0

# Enable on boot
sudo systemctl enable wg-quick@wg0

# Check status
sudo wg show

Client Configuration

# Client configuration (laptop/desktop)
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = CLIENT1_PRIVATE_KEY_HERE
Address = 10.0.0.2/24
DNS = 192.168.1.100  # Pi-hole on your homelab

[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
PresharedKey = CLIENT1_PRESHARED_KEY_HERE
Endpoint = your-public-ip-or-ddns:51820
# Route ALL traffic through the VPN (full tunnel)
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Split Tunneling

Full tunneling routes all client traffic through the VPN, which uses your home internet bandwidth for everything. Split tunneling only routes traffic destined for your homelab network through the VPN:

# Split tunnel: only route homelab traffic through VPN
[Interface]
PrivateKey = CLIENT1_PRIVATE_KEY_HERE
Address = 10.0.0.2/24
# Note: DNS is NOT set here for split tunnel
# Your local DNS handles everything else

[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
Endpoint = your-public-ip-or-ddns:51820
# Only route these subnets through the VPN:
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25

# Full tunnel: route everything through VPN
# AllowedIPs = 0.0.0.0/0, ::/0
Tip: Use split tunneling when you want to access homelab services remotely without impacting general internet performance. Use full tunneling when you are on an untrusted network (airport WiFi, hotel) and want all your traffic encrypted and routed through your home connection.

Firewall Configuration

# Allow WireGuard traffic through the firewall
sudo ufw allow 51820/udp comment "WireGuard"

# If using iptables directly:
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT

# Allow traffic from the VPN subnet to the LAN
sudo iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# Make iptables rules persistent
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

Mobile Clients

WireGuard has official apps for iOS and Android. Generate a configuration file and display it as a QR code for easy mobile setup:

# Install qrencode
sudo apt install -y qrencode

# Create a mobile client configuration
cat > phone.conf <<EOF
[Interface]
PrivateKey = PHONE_PRIVATE_KEY_HERE
Address = 10.0.0.3/24
DNS = 192.168.1.100

[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
Endpoint = your-public-ip-or-ddns:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

# Generate QR code for the mobile app to scan
qrencode -t ansiutf8 < phone.conf

# Or generate a PNG image
qrencode -t png -o phone-wireguard.png < phone.conf

Docker Integration with wg-easy

wg-easy provides a web UI for managing WireGuard clients, making it much simpler to add and remove peers:

# docker-compose.yml for wg-easy
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    container_name: wg-easy
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1
    environment:
      WG_HOST: your-ddns-hostname.example.com
      PASSWORD_HASH: '$$2a$$12$$hashed_password_here'
      WG_PORT: 51820
      WG_DEFAULT_ADDRESS: 10.8.0.x
      WG_DEFAULT_DNS: 192.168.1.100
      WG_ALLOWED_IPS: 0.0.0.0/0, ::/0
      WG_PERSISTENT_KEEPALIVE: 25
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"  # Web UI
    volumes:
      - wg_easy_data:/etc/wireguard

volumes:
  wg_easy_data:

Site-to-Site VPN

Connect two networks (e.g., your home and a VPS) so devices on either network can communicate directly:

# Site A: Home (192.168.1.0/24) - wg0.conf
[Interface]
PrivateKey = SITE_A_PRIVATE_KEY
Address = 10.0.0.1/24
ListenPort = 51820
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]
PublicKey = SITE_B_PUBLIC_KEY
Endpoint = vps.example.com:51820
AllowedIPs = 10.0.0.2/32, 10.10.10.0/24
PersistentKeepalive = 25

# Site B: VPS (10.10.10.0/24) - wg0.conf
[Interface]
PrivateKey = SITE_B_PRIVATE_KEY
Address = 10.0.0.2/24
ListenPort = 51820
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]
PublicKey = SITE_A_PUBLIC_KEY
Endpoint = home.example.com:51820
AllowedIPs = 10.0.0.1/32, 192.168.1.0/24
PersistentKeepalive = 25
Warning: For site-to-site VPN, both sites need either a public IP or dynamic DNS. If both sites are behind NAT without a public IP, you will need a relay server (a cheap VPS) in the middle that both sites connect to. Also ensure you do not have overlapping subnets between sites.

Dynamic DNS for Home Connections

Most home internet connections have dynamic IP addresses. Use a DDNS service so your VPN clients can always find your server:

# Using DuckDNS (free)
# Add to crontab:
*/5 * * * * curl -s "https://www.duckdns.org/update?domains=myhomelab&token=YOUR-TOKEN&ip=" > /dev/null

# Or run ddclient in Docker
docker run -d \
  --name ddclient \
  --restart unless-stopped \
  -v ddclient_config:/config \
  lscr.io/linuxserver/ddclient:latest

# /config/ddclient.conf
protocol=cloudflare
zone=example.com
login=token
password=your-cloudflare-api-token
home.example.com

Monitoring WireGuard

# Check WireGuard status
sudo wg show

# Output includes:
# - Each peer's public key
# - Latest handshake time
# - Data transferred
# - Endpoint IP:port

# Monitor with Prometheus using wireguard_exporter
docker run -d \
  --name wg-exporter \
  --cap-add NET_ADMIN \
  --network host \
  mindflavor/prometheus-wireguard-exporter

# Then add to prometheus.yml:
# - job_name: wireguard
#   static_configs:
#     - targets: ['localhost:9586']

With usulnet's agent architecture, you can manage Docker containers across sites connected by WireGuard. The agent on each remote site communicates with the master over your VPN, giving you unified container management across all your locations.

Security Best Practices

  • Use preshared keys for an additional layer of post-quantum security.
  • Restrict AllowedIPs to the minimum necessary. Do not give every client access to your entire network.
  • Rotate keys periodically. Generate new key pairs quarterly.
  • Use a non-standard port (not 51820) to reduce scan noise.
  • Monitor handshake timestamps. A peer that has not handshaked recently may indicate a compromised or decommissioned device.
  • Immediately remove peers for lost or decommissioned devices.