LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

WireGuard VPN: Fast, Modern, and Actually Understandable

wireguardvpnnetworkingsecuritylinuxhomelabprivacy

WireGuard is what VPN software should have always been. The reference implementation is around 4,000 lines of code — OpenVPN is over 100,000. It lives in the Linux kernel. It connects in under 100 milliseconds. Its cryptography is modern and non-negotiable: no cipher suites to misconfigure, no weak defaults to stumble into. If you’ve avoided VPNs because they were complicated, WireGuard is the exception.

This guide covers everything from the first key pair to a production multi-peer deployment with split tunneling, road warrior clients, and a site-to-site tunnel.


How WireGuard Works

WireGuard is fundamentally different from traditional VPNs:

No handshake protocol. WireGuard uses Noise Protocol Framework — when a packet arrives, the interface either decrypts it successfully (peer is authenticated) or silently drops it (packet is not from a known peer). There is no negotiation phase to attack.

No connection state. WireGuard is stateless from the server’s perspective. A peer that goes offline simply stops sending packets; the server doesn’t know or care. When it comes back, it picks up instantly.

Cryptokey routing. Every peer has a public key. The kernel routes packets to a peer based on which public key can decrypt them and which allowed IP ranges are associated with that peer. This replaces the complex certificate infrastructure of OpenVPN/IPsec.

Roaming built-in. A mobile peer can switch from Wi-Fi to cellular mid-session. As long as the next packet arrives at the server, the server updates the peer’s endpoint automatically. The tunnel survives network changes transparently.

UDP only. WireGuard uses UDP exclusively, on port 51820 by default. This means it can be blocked by firewalls that block non-TCP traffic on non-standard ports. For hostile networks, a workaround exists (see: port 443 and masquerading).

The Cryptography

WireGuard’s crypto stack is fixed and non-negotiable — there are no options to weaken it:

Component Algorithm
Key exchange Curve25519 (ECDH)
Symmetric encryption ChaCha20Poly1305 (AEAD)
Hashing/MAC BLAKE2s
Header protection SipHash24
Forward secrecy Built-in via ephemeral keys

There is no “WireGuard with AES” or “WireGuard with SHA1.” The protocol is the algorithm.


Installation

Linux (kernel 5.6+)

WireGuard is built into the Linux kernel as of 5.6. Install only the userspace tools:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Debian / Ubuntu
apt install wireguard wireguard-tools

# RHEL / Rocky / AlmaLinux
dnf install wireguard-tools

# Fedora
dnf install wireguard-tools

# Arch
pacman -S wireguard-tools

# Alpine
apk add wireguard-tools

macOS

1
2
3
4
# Homebrew
brew install wireguard-tools

# Or use the official macOS app from the App Store (easier for desktop use)

Windows / iOS / Android

Use the official apps from the WireGuard project. All platforms use the same key format and config file structure.


Core Concepts Before You Configure

Keys

Every WireGuard interface has a private key and a corresponding public key. The private key never leaves the machine it was generated on. The public key is shared with peers.

1
2
3
4
5
6
7
8
# Generate a private key
wg genkey

# Derive the public key from a private key
wg genkey | tee privatekey | wg pubkey > publickey

# Generate a preshared key (optional — adds a second layer of symmetric encryption)
wg genpsk

Interfaces

WireGuard creates a virtual network interface (e.g., wg0). You assign it an IP address just like any other interface. All VPN traffic enters and exits through this interface.

Peers

A peer is any other WireGuard node you want to communicate with. You define a peer by:

  • Its public key (required)
  • Its endpoint (IP:port) — optional; the interface will update this automatically from incoming traffic
  • Its allowed IPs — the IP ranges that should be routed to this peer (and that this peer is allowed to send from)
  • A preshared key — optional extra symmetric key for post-quantum safety

Allowed IPs and Routing

AllowedIPs does double duty:

  • Outbound: traffic destined for these CIDRs is sent to this peer
  • Inbound: traffic arriving from this peer is only accepted if its source IP is in this list

For a server peer entry (from the client’s perspective), AllowedIPs = 0.0.0.0/0 means “route all traffic through this peer” — full tunnel. AllowedIPs = 10.0.0.0/8 means “only route traffic for the 10.0.0.0/8 subnet through this peer” — split tunnel.

For a client peer entry (from the server’s perspective), AllowedIPs = 10.0.0.2/32 means “only accept traffic from this peer if it claims to be 10.0.0.2.”


Setting Up a WireGuard Server

Generate Server Keys

1
2
3
4
5
6
7
# On the server
cd /etc/wireguard
umask 077   # Ensure private key is only readable by root
wg genkey | tee server_private.key | wg pubkey > server_public.key

cat server_private.key   # Keep this secret
cat server_public.key    # Share this with clients

Create the Server Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# /etc/wireguard/wg0.conf

[Interface]
# The server's private key
PrivateKey = <server_private_key>

# IP address of this VPN interface (the server's VPN IP)
Address = 10.0.0.1/24

# Port to listen on
ListenPort = 51820

# Enable IP forwarding and NAT so VPN clients can reach the internet
# Replace eth0 with your actual internet-facing interface
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# IPv6 NAT (if needed)
# PostUp = ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# PostDown = ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# --- Peers are added below ---

Enable IP Forwarding

The server must forward packets between the VPN interface and the internet:

1
2
3
4
5
6
7
8
# Enable immediately
echo 1 > /proc/sys/net/ipv4/ip_forward

# Make permanent
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
# or create a dedicated file:
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-wireguard.conf
sysctl --system

Start and Enable the Interface

1
2
3
4
5
6
7
8
# Bring up the interface
wg-quick up wg0

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

# Check status
wg show

Open the Firewall

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# UFW (Ubuntu)
ufw allow 51820/udp
ufw allow OpenSSH  # Don't lock yourself out
ufw enable

# firewalld (Fedora/RHEL)
firewall-cmd --permanent --add-port=51820/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload

# iptables directly
iptables -A INPUT -p udp --dport 51820 -j ACCEPT

Adding Peers (Clients)

Generate Peer Keys (on the client machine)

1
2
3
wg genkey | tee peer_private.key | wg pubkey > peer_public.key
# Optional preshared key for extra security:
wg genpsk > peer_preshared.key

Add Peer to Server Config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Add to /etc/wireguard/wg0.conf on the SERVER

[Peer]
# Human-readable comment (optional)
# Name: Alice's laptop

# The peer's public key (generated on the peer)
PublicKey = <peer_public_key>

# Optional: preshared key for additional security
PresharedKey = <preshared_key>

# The VPN IP assigned to this peer (what the server accepts packets from)
AllowedIPs = 10.0.0.2/32

# Optional: uncomment to keep the tunnel alive through NAT (if peer is behind NAT)
# PersistentKeepalive = 25

Reload Server Config Without Disconnecting Others

1
2
3
4
5
# Add peer without restarting the interface (hot reload)
wg addpeer wg0 <peer_public_key> allowed-ips 10.0.0.2/32

# Or sync the config file to the running interface:
wg syncconf wg0 <(wg-quick strip wg0)

wg-quick strip removes the [Interface] directives that wg doesn’t understand, leaving only the peer configuration.


Client Configuration

Full Tunnel (Route All Traffic Through VPN)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# /etc/wireguard/wg0.conf on the CLIENT

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

# Optional: set custom DNS when tunnel is up
DNS = 10.0.0.1

[Peer]
PublicKey = <server_public_key>
PresharedKey = <preshared_key>

# The server's real internet IP and port
Endpoint = vpn.example.com:51820

# Route ALL traffic through the VPN (full tunnel)
AllowedIPs = 0.0.0.0/0, ::/0

# Keep the tunnel alive if behind NAT (mobile devices, home routers)
PersistentKeepalive = 25
1
wg-quick up wg0

Split Tunnel (Only Route Specific Traffic Through VPN)

Only traffic destined for the 10.0.0.0/24 VPN subnet and 192.168.1.0/24 (the server’s LAN) goes through the VPN. Everything else (internet browsing) uses the normal default route:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[Interface]
PrivateKey = <peer_private_key>
Address = 10.0.0.2/24

[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.example.com:51820

# Only route VPN and remote LAN traffic through tunnel
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24

PersistentKeepalive = 25

The AllowedIPs Calculator

Constructing split-tunnel CIDR lists that exclude specific ranges from full-tunnel mode is arithmetically tedious. Use the WireGuard AllowedIPs Calculator (available at several online tools — search “WireGuard AllowedIPs calculator”) or this approach:

1
2
3
4
5
# Start with 0.0.0.0/0, subtract your LAN subnet and any other exclusions
# The calculator generates the exact CIDR list needed
# Example: full tunnel EXCEPT 192.168.1.0/24:
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1, ::/0
# Then use a PostUp/PreDown hook to add a route exclusion for the LAN

Road Warrior Setup: Laptops and Mobile Devices

“Road warrior” describes a client that roams — different coffee shops, hotels, cellular networks. WireGuard is ideal for this: the tunnel survives network changes automatically.

Mobile App Configuration (iOS / Android)

The easiest method is QR code. Generate the client config, convert to QR code, scan with the app:

1
2
3
4
5
6
7
8
# Install qrencode
apt install qrencode

# Generate QR code from client config
qrencode -t ansiutf8 < /etc/wireguard/mobile-client.conf

# Or save as PNG
qrencode -o mobile-client.png < /etc/wireguard/mobile-client.conf

Scan with the WireGuard app on your phone. Done.

Generating Configs Programmatically

For managing many peers, automate key generation and config creation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env bash
# new-peer.sh — Generate a new WireGuard peer config

set -euo pipefail

SERVER_PUBLIC_KEY="<your_server_public_key>"
SERVER_ENDPOINT="vpn.example.com:51820"
VPN_SERVER_IP="10.0.0.1"
PEER_NAME="${1:?Usage: $0 <peer-name> <peer-vpn-ip>}"
PEER_VPN_IP="${2:?}"
DNS_SERVER="${3:-10.0.0.1}"
OUTPUT_DIR="/etc/wireguard/peers"

mkdir -p "$OUTPUT_DIR"

# Generate keys
PRIVATE_KEY=$(wg genkey)
PUBLIC_KEY=$(echo "$PRIVATE_KEY" | wg pubkey)
PSK=$(wg genpsk)

# Write client config
cat > "${OUTPUT_DIR}/${PEER_NAME}.conf" << EOF
[Interface]
PrivateKey = ${PRIVATE_KEY}
Address = ${PEER_VPN_IP}/24
DNS = ${DNS_SERVER}

[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
PresharedKey = ${PSK}
Endpoint = ${SERVER_ENDPOINT}
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

chmod 600 "${OUTPUT_DIR}/${PEER_NAME}.conf"

# Print server-side peer block to add to wg0.conf
echo ""
echo "=== Add to /etc/wireguard/wg0.conf on server ==="
echo ""
echo "[Peer]"
echo "# Name: ${PEER_NAME}"
echo "PublicKey = ${PUBLIC_KEY}"
echo "PresharedKey = ${PSK}"
echo "AllowedIPs = ${PEER_VPN_IP}/32"
echo ""
echo "Then run: wg syncconf wg0 <(wg-quick strip wg0)"
echo ""
echo "QR code for mobile:"
qrencode -t ansiutf8 < "${OUTPUT_DIR}/${PEER_NAME}.conf"

Site-to-Site VPN

Connect two private networks so machines on each side can reach each other directly. Common use case: connect a home lab to a cloud VPC, or two office networks.

Home LAN (192.168.1.0/24)          Cloud VPC (172.16.0.0/16)
     │                                    │
  wg0: 10.0.0.1  ←──── tunnel ────→  wg0: 10.0.0.2
     │                                    │
  router/gateway                     cloud gateway

Site A Config (Home)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /etc/wireguard/wg0.conf on Site A gateway

[Interface]
PrivateKey = <site_a_private_key>
Address = 10.0.0.1/30
ListenPort = 51820

# Forward between WireGuard and LAN interfaces
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

[Peer]
PublicKey = <site_b_public_key>
Endpoint = cloud-gateway.example.com:51820
# Site B's VPN IP + Site B's LAN subnet
AllowedIPs = 10.0.0.2/32, 172.16.0.0/16
PersistentKeepalive = 25

Site B Config (Cloud)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# /etc/wireguard/wg0.conf on Site B gateway

[Interface]
PrivateKey = <site_b_private_key>
Address = 10.0.0.2/30
ListenPort = 51820

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

[Peer]
PublicKey = <site_a_public_key>
Endpoint = home.example.com:51820
# Site A's VPN IP + Site A's LAN subnet
AllowedIPs = 10.0.0.1/32, 192.168.1.0/24
PersistentKeepalive = 25

Add Routes on Each LAN

Machines on Site A’s LAN need to know to send traffic for Site B’s subnet to the WireGuard gateway:

1
2
3
4
5
# On Site A's router (or each machine if no central routing)
ip route add 172.16.0.0/16 via 192.168.1.1   # Where 192.168.1.1 is the WG gateway

# On Site B's router
ip route add 192.168.1.0/24 via 172.16.0.1   # Where 172.16.0.1 is the WG gateway

DNS Over WireGuard

Run a DNS server on the VPN server and have clients use it. This prevents DNS leaks and allows resolving internal hostnames.

Option 1: Use the Server’s Stub Resolver

1
2
# In client [Interface] section
DNS = 10.0.0.1

The server needs to accept DNS queries on its WireGuard interface:

1
2
3
4
5
6
# If using systemd-resolved on the server
# Edit /etc/systemd/resolved.conf:
[Resolve]
DNSStubListenerExtra=10.0.0.1

systemctl restart systemd-resolved

Option 2: Pi-hole or AdGuard Home

Run Pi-hole or AdGuard Home on the VPN server. Clients set DNS = 10.0.0.1 (or the VPN server’s address) and get ad-blocking for all devices when on VPN.

Preventing DNS Leaks

With full-tunnel mode, some OSes still send DNS queries outside the tunnel. Prevent this:

1
2
3
4
5
6
# In client config — the DNS entry combined with full tunnel AllowedIPs
# handles this on most systems, but explicitly:
[Interface]
DNS = 10.0.0.1
# Some clients also support:
# DNS = 10.0.0.1, ~.    (route ALL DNS through this resolver, macOS/iOS)

wg and wg-quick Reference

wg — Low-Level Interface Tool

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Show all WireGuard interfaces and peer status
wg show

# Show specific interface
wg show wg0

# Show just the public key of an interface
wg show wg0 public-key

# Show peer endpoints and handshake times
wg show wg0 endpoints
wg show wg0 latest-handshakes

# Add a peer without editing the config file
wg set wg0 peer <PUBLIC_KEY> \
  allowed-ips 10.0.0.5/32 \
  endpoint 1.2.3.4:51820 \
  persistent-keepalive 25

# Remove a peer
wg set wg0 peer <PUBLIC_KEY> remove

# Save current running config to file
wg showconf wg0 > /etc/wireguard/wg0.conf

wg-quick — Config File Manager

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Bring interface up (reads /etc/wireguard/wg0.conf)
wg-quick up wg0

# Bring interface down
wg-quick down wg0

# Save running config back to file
wg-quick save wg0

# Strip Interface-only directives (for use with wg syncconf)
wg-quick strip wg0

Hot Reload Without Disconnecting Peers

1
2
3
# Edit /etc/wireguard/wg0.conf to add/modify peers, then:
wg syncconf wg0 <(wg-quick strip wg0)
# Existing tunnels stay up; only peers are updated

Monitoring and Troubleshooting

Check Tunnel Status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Full status: peers, endpoints, handshake times, traffic counters
wg show

# Sample output:
# interface: wg0
#   public key: AbCdEf...
#   private key: (hidden)
#   listening port: 51820
#
# peer: XyZaBc...
#   endpoint: 203.0.113.10:51234
#   allowed ips: 10.0.0.2/32
#   latest handshake: 23 seconds ago
#   transfer: 1.44 MiB received, 892 KiB sent
#   persistent keepalive: every 25 seconds

Key things to check:

  • latest handshake — should be recent (< 3 minutes if keepalive is set). “Never” means no successful connection yet.
  • transfer — if bytes aren’t increasing, traffic isn’t flowing
  • endpoint — should show the peer’s current IP:port

Common Issues

Handshake never completes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. Check the server is listening
ss -ulnp | grep 51820

# 2. Check firewall is allowing UDP 51820
iptables -L INPUT -n | grep 51820
ufw status

# 3. Verify the server's public key in the client config matches
wg show wg0 public-key    # on server
# Compare with [Peer] PublicKey in client config

# 4. Check the endpoint IP/hostname resolves correctly
dig vpn.example.com

Handshake succeeds but no traffic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. Check IP forwarding is enabled on the server
sysctl net.ipv4.ip_forward
# Should be: net.ipv4.ip_forward = 1

# 2. Check NAT/masquerade rules are active
iptables -t nat -L POSTROUTING -n -v | grep MASQUERADE

# 3. Verify AllowedIPs covers the traffic you're trying to send
wg show wg0 allowed-ips

# 4. Check routing table on client
ip route show table main
ip route show table 51820   # wg-quick creates a separate routing table

Can ping VPN IP but not through to internet:

1
2
3
4
5
6
# On the server, verify outbound interface name
ip route get 8.8.8.8 | grep dev
# Use that interface name in PostUp MASQUERADE rule, not 'eth0'

# Also check FORWARD chain
iptables -L FORWARD -n -v

DNS not working through VPN:

1
2
3
4
5
# Check if DNS queries are going through the tunnel
tcpdump -i wg0 udp port 53

# On client, test DNS explicitly through VPN server IP
dig @10.0.0.1 google.com

WireGuard blocked by firewall (corporate network, hotel Wi-Fi):

1
2
3
4
5
6
# Option 1: Move to port 443 UDP (less likely to be blocked)
# On server: change ListenPort = 443
# On client: change Endpoint = vpn.example.com:443

# Option 2: Use wstunnel to wrap WireGuard in WebSocket over port 443 TCP
# This makes WireGuard look like HTTPS traffic to DPI firewalls

Traffic Monitoring

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Watch bandwidth usage per peer in real time
watch -n1 'wg show wg0 transfer'

# More detailed with vnstat
vnstat -i wg0 -l

# Packet capture on WireGuard interface (shows decrypted traffic)
tcpdump -i wg0 -n

# Packet capture on physical interface (shows encrypted WireGuard UDP)
tcpdump -i eth0 -n udp port 51820

Key Management Best Practices

Rotate Keys Periodically

WireGuard provides forward secrecy through ephemeral session keys, but if a device is compromised, the static private key is exposed. Rotate keys annually or when a device is decommissioned:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env bash
# rotate-wireguard-key.sh — Rotate this machine's WireGuard key
set -euo pipefail

INTERFACE="${1:-wg0}"
CONFIG="/etc/wireguard/${INTERFACE}.conf"

OLD_PRIVATE=$(wg show "$INTERFACE" private-key)
NEW_PRIVATE=$(wg genkey)
NEW_PUBLIC=$(echo "$NEW_PRIVATE" | wg pubkey)

# Update config file
sed -i "s|PrivateKey = .*|PrivateKey = ${NEW_PRIVATE}|" "$CONFIG"

# Apply new key without full restart
wg set "$INTERFACE" private-key <(echo "$NEW_PRIVATE")

echo "New public key: $NEW_PUBLIC"
echo "Update this on all peers that have this machine as a peer."

Use Preshared Keys for Post-Quantum Safety

The Curve25519 key exchange is vulnerable to a sufficiently powerful quantum computer. A preshared symmetric key (PSK) adds a layer of symmetric encryption that is quantum-resistant:

1
2
3
4
5
6
# Generate PSK
wg genpsk > /etc/wireguard/psk-alice.key
chmod 600 /etc/wireguard/psk-alice.key

# Add to both server and client [Peer] section:
PresharedKey = <contents_of_psk_file>

Store Keys Securely

1
2
3
4
5
6
7
8
9
# Private keys should be root-read-only
chmod 600 /etc/wireguard/*.conf
chmod 600 /etc/wireguard/*.key
chown root:root /etc/wireguard/*

# Verify
ls -la /etc/wireguard/
# -rw------- root root  wg0.conf
# -rw------- root root  server_private.key

Production Considerations

Multiple WireGuard Interfaces

You can run multiple WireGuard interfaces on the same machine — useful for separating client VPN traffic from site-to-site tunnels:

1
2
3
# Create wg0 for client access, wg1 for site-to-site
systemctl enable --now wg-quick@wg0
systemctl enable --now wg-quick@wg1

High Availability with ECMP or Failover

WireGuard doesn’t have built-in HA, but you can route around it:

1
2
3
4
5
# Two servers with the same VPN subnet, load-balanced with DNS round-robin
# Or use keepalived to float a VIP between two WireGuard servers

# For simple failover, use two Peer entries on the client
# with different endpoints — clients will use whichever responds

Logging

WireGuard is intentionally quiet. Enable kernel module debug logging only when troubleshooting:

1
2
3
4
5
6
7
8
# Enable detailed WireGuard kernel logging (verbose)
echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control
# Disable
echo module wireguard -p > /sys/kernel/debug/dynamic_debug/control

# View logs
dmesg | grep wireguard
journalctl -k | grep wireguard

WireGuard with NetworkManager (Desktop Linux)

1
2
3
4
5
6
7
8
# Import a WireGuard config into NetworkManager
nmcli connection import type wireguard file /etc/wireguard/wg0.conf

# Connect/disconnect
nmcli connection up wg0
nmcli connection down wg0

# Or use the GUI in GNOME/KDE Settings → Network → VPN → +

Quick Reference

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Key generation
wg genkey                          # Private key
wg genkey | tee priv | wg pubkey   # Private + public
wg genpsk                          # Preshared key

# Interface management
wg-quick up wg0                    # Start
wg-quick down wg0                  # Stop
systemctl enable --now wg-quick@wg0  # Start + enable on boot
wg show                            # Status all interfaces
wg show wg0                        # Status specific interface

# Hot peer management (no restart)
wg set wg0 peer <PUBKEY> allowed-ips 10.0.0.5/32
wg set wg0 peer <PUBKEY> remove
wg syncconf wg0 <(wg-quick strip wg0)

# Diagnostics
wg show wg0 latest-handshakes      # Handshake timestamps
wg show wg0 transfer               # Bytes in/out per peer
wg show wg0 endpoints              # Current peer endpoints
tcpdump -i wg0                     # Decrypted VPN traffic

WireGuard’s simplicity is its most underrated feature. The configuration file for a client is eight lines. The entire protocol can be read and understood in a weekend. When something breaks, there’s almost nowhere to hide — it’s either a key mismatch, a firewall rule, or a routing issue. That debuggability alone makes it worth choosing over every VPN that came before it.

Comments