wireguard
Identity
- Kernel module:
wireguard(built into kernel 5.6+; backported to 5.4 via DKMS) - Config:
/etc/wireguard/wg0.conf(interface name matches filename:wg0) - Manage via:
wg-quick up wg0/wg-quick down wg0/wg(status and stats) - Unit:
wg-quick@wg0.service(systemd unit for persistent operation) - Install:
apt install wireguard/dnf install wireguard-tools(kernel module usually pre-included on modern distros)
Key Generation
# Generate server keys
wg genkey | tee server_private.key | wg pubkey > server_public.key
# Generate client keys
wg genkey | tee client_private.key | wg pubkey > client_public.key
# Optional: pre-shared key (adds post-quantum resistance layer)
wg genpsk > preshared.key
chmod 600 server_private.key client_private.key preshared.key
Private keys never leave the host they were generated on. Public keys are shared freely. Pre-shared keys (PSKs) are symmetric secrets shared between exactly two peers — one PSK per peer pair, stored in both configs.
Key Operations
| Goal | Command |
|---|---|
| Bring interface up | sudo wg-quick up wg0 |
| Bring interface down | sudo wg-quick down wg0 |
| Enable on boot | sudo systemctl enable wg-quick@wg0 |
| Start now + on boot | sudo systemctl enable --now wg-quick@wg0 |
| Show all interface status | sudo wg show |
| Show specific interface | sudo wg show wg0 |
| Show current running config | sudo wg showconf wg0 |
| Check handshake timestamps | sudo wg show wg0 latest-handshakes |
| Show transfer stats | sudo wg show wg0 transfer |
| Add peer at runtime | sudo wg set wg0 peer <pubkey> allowed-ips 10.0.0.3/32 |
| Save runtime changes to file | sudo wg-quick save wg0 |
| Reload config without down/up | sudo wg syncconf wg0 <(wg-quick strip wg0) |
Expected Ports
- 51820/udp — default; set via
ListenPortin[Interface] - WireGuard is UDP-only. TCP is not supported.
- Verify listening:
ss -ulnp | grep wireguard - Allow through firewall:
ufw allow 51820/udporfirewall-cmd --add-port=51820/udp --permanent
Health Checks
sudo wg show— lists all interfaces and peers; confirms the interface is upsudo wg show wg0 latest-handshakes— epoch timestamps; any value > 0 means the peer has connected; values more than ~3 minutes in the past mean the connection is idle or broken- Ping across the tunnel (e.g.,
ping 10.0.0.2from the server to the first client's tunnel IP) journalctl -u wg-quick@wg0 -n 50— startup errors, PostUp script failuressudo wg show wg0 transfer— bytes sent/received; zero RX means no traffic is returning
Common Failures
| Symptom | Likely cause | Fix |
|---|---|---|
wg show shows peer but no handshake |
Firewall blocking UDP 51820 on server, or wrong public key pasted | Open UDP port on server; verify keys match exactly |
| Handshake occurs but no traffic flows | ip_forward disabled on server; PostUp NAT rule missing |
sysctl -w net.ipv4.ip_forward=1; verify PostUp iptables rule |
| Handshake times out repeatedly | Client endpoint wrong (wrong IP or port) | Confirm server's public IP and ListenPort; check Endpoint line |
| DNS leaks outside the tunnel | DNS server not set in client [Interface], or DNS not reachable via tunnel |
Set DNS = <server-tunnel-ip> in client config; run DNS server on server |
| Mobile client drops after idle | NAT table on router expires UDP mapping | Add PersistentKeepalive = 25 to client's [Peer] (server entry) |
| Split tunnel routes wrong traffic | AllowedIPs too broad or overlapping between peers | AllowedIPs is a routing table — each IP range can appear in only one peer |
wg-quick: Permission denied on startup |
wg0.conf permissions are not 600 |
chmod 600 /etc/wireguard/wg0.conf |
RTNETLINK answers: Operation not supported |
WireGuard kernel module not loaded | modprobe wireguard; on older kernels install wireguard-dkms |
Pain Points
-
AllowedIPs is a routing table: Each peer's
AllowedIPsdetermines which destination IPs get routed through that peer's tunnel.0.0.0.0/0, ::/0= all traffic (full VPN). Specific subnets = split tunnel. Common mistake: overlappingAllowedIPsbetween two peers — WireGuard will accept the config but routing becomes undefined. -
No auto-discovery: Every peer must be explicitly added to every other peer's config. There is no central auth server or certificate authority. Adding a new client means editing the server config and reloading it.
-
UDP only: WireGuard strictly uses UDP. If a network blocks all UDP (restrictive hotel/corporate firewall), WireGuard will not work. Unlike OpenVPN, there is no TCP fallback.
-
Handshake timeout = dead connection:
wg showshowslatest handshakeas a Unix timestamp. If it is more than 3 minutes ago, the tunnel is likely broken.PersistentKeepalive = 25sends a keepalive every 25 seconds to maintain NAT mappings and detect dead connections faster. -
ip_forward required on server: For the server to route traffic between peers (or to the internet),
net.ipv4.ip_forward = 1must be active. Set persistently in/etc/sysctl.d/99-wireguard.confwithnet.ipv4.ip_forward = 1; activate withsysctl -p. -
File permissions:
/etc/wireguard/wg0.confmust be mode 600 (owner-read-only).wg-quickwarns and may refuse to start if permissions are too open. The file contains the private key in plaintext. -
Runtime changes vs. file changes:
wg setmodifies the running interface immediately but does not persist towg0.conf. Usewg-quick save wg0to write runtime state back to the file, or usewg syncconf wg0 <(wg-quick strip wg0)to apply file changes without taking the interface down.
References
See references/ for:
wg0.conf.annotated— fully annotated server and client config examplessetup-patterns.md— step-by-step guides for common deployment scenariosdocs.md— official documentation and community resources