A modern, high-performance VPN protocol built into the Linux kernel that uses state-of-the-art cryptography with a minimal codebase.

Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.

Table of Contents#

  1. Overview
  2. Key Features
  3. Components
  4. Installation
  5. Basic Configuration
  6. Advanced Configuration
  7. Key Rotation
  8. Firewall Integration
  9. Censorship Bypass
  10. Logging and Debugging
  11. Troubleshooting
  12. See Also
  13. Sources

1. Overview#

WireGuard is a VPN protocol that blends high-speed cryptography with simplicity, aiming for better performance and ease of use compared to traditional VPN solutions like IPsec and OpenVPN. It operates as a kernel module (built into Linux since kernel 5.6) and establishes encrypted point-to-point tunnels using modern cryptographic primitives.

The entire codebase is roughly 4,000 lines of code, making it feasible to audit. It uses Curve25519 for key exchange, ChaCha20 for symmetric encryption, Poly1305 for authentication, BLAKE2s for hashing, and SipHash24 for hashtable keys.

2. Key Features#

  • Efficiency and simplicity - lightweight codebase (~4,000 lines) that facilitates security auditing
  • Speed - kernel-level implementation with state-of-the-art cryptographic algorithms
  • Cross-platform support - available for Linux, Windows, macOS, FreeBSD, OpenBSD, iOS, and Android
  • Strong security - mandatory modern cryptography with no cipher negotiation (no attack surface from legacy algorithms)
  • Cryptokey routing - each peer is identified by its public key, and allowed IPs act as both ACL and routing table
  • Stealth - sends no response to unauthenticated packets, making the interface invisible to port scanners
  • Roaming - peers can change IP addresses transparently; the tunnel updates automatically

3. Components#

3.1. Keys and Cryptography#

  • Private key - a 256-bit Curve25519 secret key used exclusively for decrypting received data
  • Public key - derived from the private key; shared with peers so they can encrypt data destined for this interface
  • Preshared key (optional) - a symmetric key shared between two peers that adds a post-quantum resistance layer on top of the standard Curve25519 exchange

3.2. Interface Configuration#

  • Address - the VPN IP address(es) assigned to the local WireGuard interface
  • ListenPort - the UDP port on which the interface awaits incoming connections (default: 51820)
  • PersistentKeepalive - interval (in seconds) for sending keepalive packets to maintain NAT mappings

3.3. Peer Configuration#

  • PublicKey - the remote peer's public key
  • Endpoint - the remote peer's IP address and port (optional for peers that initiate connections)
  • AllowedIPs - IP ranges that are permitted through this peer; doubles as a routing directive
  • PresharedKey - optional symmetric key for additional encryption layer

4. Installation#

# Arch Linux
sudo pacman -S wireguard-tools

# Ubuntu/Debian
sudo apt update && sudo apt install wireguard

# CentOS/Fedora
sudo dnf install wireguard-tools

# FreeBSD
pkg install wireguard-tools

WireGuard is built into the Linux kernel since 5.6. The wireguard-tools package provides the wg and wg-quick userspace utilities. On older kernels, install the wireguard-dkms package as well.

5. Basic Configuration#

5.1. Key Generation#

Generate a private and public key pair:

wg genkey | tee privatekey | wg pubkey > publickey

Generate a preshared key for an additional symmetric encryption layer:

wg genpsk > presharedkey

Set proper permissions on key files:

chmod 600 privatekey presharedkey

5.2. Server Configuration#

Create /etc/wireguard/wg0.conf:

[Interface]
Address = 192.0.2.1/24
PrivateKey = <server-private-key>
ListenPort = 51820

[Peer]
PublicKey = <client-public-key>
PresharedKey = <preshared-key>
AllowedIPs = 192.0.2.2/32

For dual-stack (IPv4 + IPv6), specify both addresses. Use a /64 prefix for the IPv6 subnet to follow standard addressing conventions:

[Interface]
Address = 192.0.2.1/24, fd00:wg::1/64
PrivateKey = <server-private-key>
ListenPort = 51820

IPv6 recommendation: Use a /64 from the fd00::/8 Unique Local Address range for private WireGuard networks. If your ISP provides a public /48 or /56, delegate a /64 to the tunnel. Avoid subnets smaller than /64, as some features (SLAAC, neighbor discovery) expect a /64 boundary.

5.3. Client Configuration#

[Interface]
Address = 192.0.2.2/24, fd00:wg::2/64
PrivateKey = <client-private-key>
DNS = 192.0.2.1

[Peer]
PublicKey = <server-public-key>
PresharedKey = <preshared-key>
Endpoint = <server-public-ip>:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
  • AllowedIPs = 0.0.0.0/0, ::/0 routes all traffic through the tunnel (full tunnel)
  • AllowedIPs = 192.0.2.0/24 routes only the VPN subnet (split tunnel)
  • PersistentKeepalive = 25 keeps NAT mappings alive for peers behind firewalls

5.4. Starting and Stopping#

Using wg-quick:

sudo wg-quick up wg0
sudo wg-quick down wg0

Using systemd (recommended for servers):

sudo systemctl start wg-quick@wg0
sudo systemctl stop wg-quick@wg0
sudo systemctl enable wg-quick@wg0

6. Advanced Configuration#

6.1. Masquerading (NAT for Internet Access)#

To enable masquerading so VPN clients can reach the internet through the server, choose one approach based on your firewall backend:

iptables (PostUp/PostDown):

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens2 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens2 -j MASQUERADE

nftables (PostUp/PostDown):

PostUp = nft add table ip nat; nft add chain ip nat postrouting '{ type nat hook postrouting priority srcnat; }'; nft add rule ip nat postrouting oifname "%i" masquerade
PostDown = nft delete table ip nat

firewalld (no PostUp needed, persistent):

sudo firewall-cmd --zone=public --add-interface=wg0 --permanent
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload

ufw (edit before.rules):

# Add to /etc/ufw/before.rules before *filter:
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.0.2.0/24 -o ens2 -j MASQUERADE
COMMIT
# /etc/default/ufw - change:
DEFAULT_FORWARD_POLICY="ACCEPT"
sudo ufw reload

PostUp/PostDown rules are removed when the interface goes down. For persistent rules that survive interface cycling, configure your firewall backend directly.

nftables:

# /etc/nftables.d/wireguard.nft
table inet wg_nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    oifname "ens2" ip saddr 192.0.2.0/24 masquerade
  }
  chain forward {
    type filter hook forward priority 0; policy accept;
    iifname "wg0" accept
    oifname "wg0" ct state established,related accept
  }
}
sudo nft -f /etc/nftables.d/wireguard.nft
# Add include to /etc/nftables.conf for boot persistence

iptables:

sudo iptables -A FORWARD -i wg0 -j ACCEPT
sudo iptables -A FORWARD -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o ens2 -j MASQUERADE

# Persist (Arch)
iptables-save | sudo tee /etc/iptables/iptables.rules && sudo systemctl enable iptables
# Persist (Debian)
sudo netfilter-persistent save

firewalld and ufw rules from Section 6.1 are already persistent.

6.3. Multiple Peers#

Add additional [Peer] blocks to the same configuration file:

[Peer]
# Laptop
PublicKey = <laptop-public-key>
AllowedIPs = 192.0.2.2/32

[Peer]
# Phone
PublicKey = <phone-public-key>
AllowedIPs = 192.0.2.3/32

[Peer]
# Site-to-site gateway
PublicKey = <gateway-public-key>
AllowedIPs = 192.0.2.4/32, 198.51.100.0/24
PersistentKeepalive = 25

6.4. DNS Configuration#

For full-tunnel configurations, set a DNS server on the client:

[Interface]
DNS = 192.0.2.1
# Or use public resolvers:
# DNS = 1.1.1.1, 2606:4700:4700::1111

wg-quick sets the DNS using resolvconf or systemd-resolved depending on the system. If using systemd-resolved:

# Check current DNS routing
resolvectl status wg0

7. Key Rotation#

WireGuard does not have built-in key rotation. You must rotate keys manually or with automation.

7.1. Manual Key Rotation Procedure#

On the peer being rotated:

# Generate new keys
wg genkey | tee /etc/wireguard/new_privatekey | wg pubkey > /etc/wireguard/new_publickey

# Update the local config with the new private key
# Edit /etc/wireguard/wg0.conf: replace PrivateKey value

# Distribute the new public key to all peers that reference this peer
# On each remote peer, update the PublicKey value in the [Peer] section

# Restart the interface
sudo systemctl restart wg-quick@wg0

7.2. Live Key Rotation (No Downtime)#

Use the wg set command for hot-swapping without restarting the interface:

# On the server, update the peer's public key
sudo wg set wg0 peer <old-public-key> remove
sudo wg set wg0 peer <new-public-key> allowed-ips 192.0.2.2/32

# On the peer, update the private key
sudo wg set wg0 private-key /etc/wireguard/new_privatekey

After live rotation, update the configuration files to match. Otherwise, the next wg-quick down/up cycle will revert to the old keys. Avoid SaveConfig = true in production, as it writes runtime state back to the config file and can cause unexpected changes.

7.3. Preshared Key Rotation#

# Generate a new preshared key
wg genpsk > /etc/wireguard/new_psk

# Update both sides simultaneously (brief interruption expected)
# On server:
sudo wg set wg0 peer <client-pubkey> preshared-key /etc/wireguard/new_psk
# On client:
sudo wg set wg0 peer <server-pubkey> preshared-key /etc/wireguard/new_psk

7.4. Rotation Schedule Recommendations#

Key typeRecommended intervalNotes
Private/public keypairEvery 90 daysRotate more frequently for high-security environments
Preshared keyEvery 30 daysBoth peers must update simultaneously
Server keypairEvery 6-12 monthsRequires updating all client configs

8. Firewall Integration#

8.1. Required Firewall Rules#

At minimum, allow the WireGuard listen port and forwarding:

# Allow WireGuard UDP port
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT

# Allow forwarding between WireGuard and the physical interface
sudo iptables -A FORWARD -i wg0 -o ens2 -j ACCEPT
sudo iptables -A FORWARD -i ens2 -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

8.2. Restricting Access Between Peers#

By default, WireGuard allows all traffic between connected peers. To restrict peer-to-peer communication:

table inet wg_filter {
  chain forward {
    type filter hook forward priority 0; policy drop;
    # Allow peers to reach the server subnet only
    iifname "wg0" oifname "ens2" accept
    iifname "ens2" oifname "wg0" ct state established,related accept
    # Block peer-to-peer (wg0 -> wg0 is implicitly dropped)
  }
}

9. Censorship Bypass#

In censored environments where WireGuard's UDP traffic is blocked, route it through a VLESS or WebSocket tunnel. See the Censorship Bypass Guide Section 9 for:

  • Direct stacking (sing-box TUN + WireGuard)
  • OPNsense gateway routing
  • wstunnel (WireGuard UDP over WebSocket)

Key considerations:

  • Set PersistentKeepalive = 45 (not 25) to reduce traffic pattern fingerprinting
  • Use specific AllowedIPs (not 0.0.0.0/0) to avoid routing conflicts with the outer tunnel
  • WireGuard's UDP handshake survives tunnel restarts automatically

10. Logging and Debugging#

10.1. Enable Kernel Debug Logging#

WireGuard runs in the kernel and uses dynamic debug for logging:

# Enable verbose WireGuard logging
echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control

# View logs
sudo dmesg -wT | grep wireguard
# Or with journalctl
sudo journalctl -k --grep=wireguard -f

Disable debug logging when finished, as it generates significant output:

echo 'module wireguard -p' | sudo tee /sys/kernel/debug/dynamic_debug/control

10.2. Interface Status#

# Show interface status, peers, transfer stats, and last handshake
sudo wg show
sudo wg show wg0

# Show only specific fields
sudo wg show wg0 latest-handshakes
sudo wg show wg0 transfer
sudo wg show wg0 endpoints

10.3. Packet Capture#

# Capture on the WireGuard interface (decrypted traffic)
sudo tcpdump -i wg0 -n

# Capture on the physical interface (encrypted UDP packets)
sudo tcpdump -i ens2 -n udp port 51820

10.4. Common Debug Checks#

# Verify the kernel module is loaded
lsmod | grep wireguard

# Check if IP forwarding is enabled
sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding

# Enable IP forwarding (temporary)
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1

# Enable IP forwarding (persistent)
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 --system

Troubleshooting#

IssueCauseSolution
No handshake occursFirewall blocking UDP port, wrong endpoint, or key mismatchCheck sudo wg show for latest-handshakes; verify firewall allows the ListenPort; confirm public keys match
Handshake succeeds but no trafficAllowedIPs misconfigured or IP forwarding disabledVerify AllowedIPs on both sides cover the desired ranges; enable net.ipv4.ip_forward=1
Connection drops after minutesNAT timeout evicting the UDP mappingSet PersistentKeepalive = 25 on the peer behind NAT
DNS resolution fails on clientDNS not set in the Interface section, or resolver not reachable via tunnelAdd DNS = <server-ip> to the client config; verify the DNS server is accessible through the tunnel
"RTNETLINK answers: Operation not permitted"Missing CAP_NET_ADMIN or wireguard module not loadedRun with sudo; verify lsmod | grep wireguard shows the module
Slow throughputMTU issues causing fragmentationSet MTU = 1420 (or lower) in the Interface section; test with ping -M do -s 1392 <peer-ip>
Peers cannot reach each other (only server)Missing forwarding rules between wg0 interfacesAdd iptables/nftables FORWARD rules allowing wg0-to-wg0 traffic
"Key is not the correct length"Corrupted or improperly copied keyRegenerate the key pair; ensure no trailing whitespace or newlines in the config

See Also#

Sources#