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/0 routes all traffic through the VPN (full tunnel). For split tunneling (only VPN subnet), use AllowedIPs = 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:

  1. Check the firewall: Ensure UDP 51820 is open on the server.
    sudo ufw status | grep 51820
  2. Verify keys: The client’s [Peer] PublicKey must be the server’s public key, and vice versa. A common mistake is using the private key where the public key should go.
  3. Check the endpoint: The client must specify the correct server public IP and port in Endpoint.
  4. Check IP forwarding:
    sysctl net.ipv4.ip_forward   # Must return 1

DNS Leaks

If DNS queries bypass the VPN:

  1. Add DNS = 1.1.1.1 to the client’s [Interface] section.
  2. On systemd-resolved systems, WireGuard’s wg-quick automatically 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:

  1. Verify the PostUp iptables rules are in the server config.
  2. Make sure eth0 in the PostUp command matches your server’s actual network interface name (check with ip route | grep default).
  3. 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 Endpoint manually (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-server or Netmaker.
  • Kernel module vs. userspace: Always prefer the kernel module (wireguard-dkms or 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 = 25 for clients behind NAT.
  • Troubleshoot connectivity by checking firewall rules, key matching, IP forwarding, and the wg show output.