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.
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
|
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
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