Why WireGuard?
WireGuard is a modern, high-performance VPN protocol designed for simplicity, speed, and strong security. Unlike legacy VPN solutions like OpenVPN or IPSec, WireGuard consists of roughly 4,000 lines of code (compared to OpenVPN’s ~100,000), making it easier to audit, maintain, and deploy.
Key advantages:
- Performance: Operates as a Linux kernel module, achieving throughput comparable to raw network speeds with minimal CPU overhead.
- Simplicity: Configuration is a single flat file per interface — no certificate authorities, no complex state machines.
- Cryptography: Uses Curve25519 for key exchange, ChaCha20 for encryption, Poly1305 for authentication, and BLAKE2s for hashing — all modern, battle-tested primitives.
- Roaming: Seamlessly handles IP address changes (mobile clients switching between Wi-Fi and cellular).
This guide covers server and client setup on Linux, common connectivity problems, and troubleshooting DNS leaks and routing issues.
Prerequisites
- A Linux server with a public IP address (Ubuntu 22.04+, Debian 12+, or CentOS/RHEL 9+).
- Root or sudo access on both server and client.
- UDP port 51820 open on the server firewall.
- A Linux, macOS, Windows, iOS, or Android client.
Step-by-Step Solution
1. Install WireGuard
On Ubuntu/Debian:
sudo apt update
sudo apt install -y wireguard
On CentOS/RHEL 9:
sudo dnf install -y wireguard-tools
Verify the installation:
wg --version
2. Generate Key Pairs
On both the server and each client, generate a private/public key pair:
# Generate keys (do this on EACH machine)
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
# Secure the private key
chmod 600 /etc/wireguard/private.key
3. Configure the Server
Create /etc/wireguard/wg0.conf on the server:
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# Enable NAT for clients accessing the internet through the VPN
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Client 1
[Peer]
PublicKey = <CLIENT1_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
# Client 2
[Peer]
PublicKey = <CLIENT2_PUBLIC_KEY>
AllowedIPs = 10.0.0.3/32
Enable IP forwarding:
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
Open the firewall:
sudo ufw allow 51820/udp
Start the interface:
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
4. Configure the Client
Create /etc/wireguard/wg0.conf on the client:
[Interface]
Address = 10.0.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 1.1.1.1, 8.8.8.8
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <SERVER_PUBLIC_IP>:51820
AllowedIPs = 0.0.0.0/0 # Route ALL traffic through the VPN
PersistentKeepalive = 25 # Required if behind NAT
AllowedIPs = 0.0.0.0/0routes all traffic through the VPN (full tunnel). For split tunneling (only VPN subnet), useAllowedIPs = 10.0.0.0/24.
Start the tunnel:
sudo wg-quick up wg0
5. Verify the Connection
# Check the tunnel status
sudo wg show
# Ping the server through the tunnel
ping 10.0.0.1
# Verify your public IP has changed (full tunnel mode)
curl ifconfig.me
Troubleshooting
Connection Timeout / No Handshake
If wg show shows no “latest handshake” for a peer:
- Check the firewall: Ensure UDP 51820 is open on the server.
sudo ufw status | grep 51820 - Verify keys: The client’s
[Peer] PublicKeymust be the server’s public key, and vice versa. A common mistake is using the private key where the public key should go. - Check the endpoint: The client must specify the correct server public IP and port in
Endpoint. - Check IP forwarding:
sysctl net.ipv4.ip_forward # Must return 1
DNS Leaks
If DNS queries bypass the VPN:
- Add
DNS = 1.1.1.1to the client’s[Interface]section. - On systemd-resolved systems, WireGuard’s
wg-quickautomatically configures DNS. If not working:sudo resolvectl dns wg0 1.1.1.1 sudo resolvectl domain wg0 "~."
Client Can Reach Server But Not the Internet
This usually means NAT/masquerade is not configured on the server:
- Verify the
PostUpiptables rules are in the server config. - Make sure
eth0in the PostUp command matches your server’s actual network interface name (check withip route | grep default). - Verify IP forwarding is enabled.
Performance Issues
- MTU mismatch: WireGuard defaults to MTU 1420. If you’re behind another tunnel (e.g., PPPoE), you may need to lower it:
[Interface] MTU = 1380 - CPU bottleneck: On older kernels without the WireGuard kernel module, performance suffers because it runs in userspace. Upgrade to kernel 5.6+ for native support.
Gotchas and Edge Cases
- AllowedIPs is a routing table: It determines which destination IPs are sent through the tunnel AND which source IPs are accepted from the peer. Misconfiguring this is the #1 cause of connectivity issues.
- No dynamic IP support for peers: Unlike OpenVPN, WireGuard peers are identified by public key, not IP. If a server’s IP changes, clients must update their
Endpointmanually (or use a dynamic DNS hostname). - No built-in user authentication: WireGuard authenticates by key pair only. For multi-user setups, consider a management layer like
wg-access-serverorNetmaker. - Kernel module vs. userspace: Always prefer the kernel module (
wireguard-dkmsor kernel 5.6+) over the Go userspace implementation (wireguard-go) for production performance.
Summary
- WireGuard is a modern, fast, and simple VPN protocol built into the Linux kernel since version 5.6.
- Configuration is a single file per interface with key-based authentication — no certificates needed.
- Always enable IP forwarding and configure NAT/masquerade on the server for full-tunnel VPN setups.
- Use
PersistentKeepalive = 25for clients behind NAT. - Troubleshoot connectivity by checking firewall rules, key matching, IP forwarding, and the
wg showoutput.