WireGuard VPN Setup on Linux

WireGuard is a modern, high-performance VPN that lives inside the Linux kernel (since 5.6). Its codebase is roughly 4,000 lines of code -- two orders of magnitude smaller than OpenVPN or IPsec -- which makes it easy to audit and remarkably fast.

This guide walks through a complete WireGuard deployment: generating keys, configuring both server and client, setting up routing, enabling DNS, and connecting mobile devices.

Part of the VPN and SSH guide series. See also: OpenVPN Guide | IPsec with strongSwan | VPN Protocol Comparison


Installation

Debian / Ubuntu

sudo apt update
sudo apt install wireguard wireguard-tools

RHEL / Fedora

sudo dnf install wireguard-tools

On RHEL 8 you may need the EPEL and ELRepo repositories for the kernel module:

sudo dnf install epel-release elrepo-release
sudo dnf install kmod-wireguard wireguard-tools

Verify the module loads:

sudo modprobe wireguard
lsmod | grep wireguard

Key Generation

WireGuard uses Curve25519 key pairs. Generate them on every peer (server and each client):

# Generate private key (keep secret)
wg genkey | tee /etc/wireguard/private.key
chmod 600 /etc/wireguard/private.key

# Derive public key
cat /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key

Optionally generate a pre-shared key for an extra symmetric encryption layer (post-quantum defence):

wg genpsk > /etc/wireguard/psk.key
chmod 600 /etc/wireguard/psk.key

Server Configuration

Create /etc/wireguard/wg0.conf:

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

# Enable IP forwarding and NAT when the interface comes up
PostUp  = sysctl -w net.ipv4.ip_forward=1; \
          iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# --- Peer: laptop ---
[Peer]
PublicKey    = <client-public-key>
PresharedKey = <psk>
AllowedIPs   = 10.0.0.2/32

Key directives

Directive Purpose
Address The VPN address of this peer (CIDR notation).
ListenPort UDP port WireGuard listens on. Default 51820.
PrivateKey This peer's private key.
AllowedIPs Which source IPs are accepted from the peer and which destinations are routed to it.

Client Configuration

On the client create /etc/wireguard/wg0.conf:

[Interface]
Address    = 10.0.0.2/24
PrivateKey = <client-private-key>
DNS        = 1.1.1.1, 9.9.9.9

[Peer]
PublicKey    = <server-public-key>
PresharedKey = <psk>
Endpoint     = vpn.example.com:51820
AllowedIPs   = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Setting AllowedIPs = 0.0.0.0/0, ::/0 routes all traffic through the VPN (full tunnel). For split tunnelling, list only the subnets that should traverse the tunnel, e.g. AllowedIPs = 10.0.0.0/24, 192.168.1.0/24.

PersistentKeepalive = 25 sends a keepalive packet every 25 seconds, which is essential when the client sits behind NAT.

DNS Configuration

The DNS directive in the [Interface] section is handled by wg-quick. It temporarily rewrites /etc/resolv.conf (or uses resolvconf / systemd-resolved if available). To push DNS via systemd-resolved explicitly:

# /etc/wireguard/wg0.conf  (client)
PostUp  = resolvectl dns %i 10.0.0.1; resolvectl domain %i "~."
PostDown = resolvectl revert %i

Bringing the Tunnel Up

# Start the tunnel
sudo wg-quick up wg0

# Check status
sudo wg show

# Enable at boot
sudo systemctl enable wg-quick@wg0

Sample wg show output:

interface: wg0
  public key: aB3d...
  private key: (hidden)
  listening port: 51820

peer: xY7q...
  endpoint: 203.0.113.10:48372
  allowed ips: 10.0.0.2/32
  latest handshake: 12 seconds ago
  transfer: 1.48 GiB received, 3.21 GiB sent

Routing and Firewall

Open the WireGuard port in your firewall:

# nftables
sudo nft add rule inet filter input udp dport 51820 accept

# or iptables
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT

If the server is a gateway for the VPN clients, ensure IP forwarding is persistent:

echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system

Mobile Clients

The official WireGuard apps for Android and iOS can import a configuration file or scan a QR code. Generate a QR code from an existing config:

sudo apt install qrencode
qrencode -t ansiutf8 < /etc/wireguard/client-mobile.conf

The QR code appears directly in the terminal. Point the mobile app's camera at it to import.

Adding and Removing Peers at Runtime

You do not need to restart the interface to add a peer:

sudo wg set wg0 peer <new-public-key> allowed-ips 10.0.0.3/32

Remove a peer:

sudo wg set wg0 peer <public-key> remove

These changes are not written to wg0.conf automatically. Save the running config with:

sudo wg-quick save wg0

Troubleshooting

Symptom Likely Cause
Handshake never completes Firewall blocks UDP 51820; wrong public key; endpoint unreachable.
Handshake OK but no traffic AllowedIPs mismatch; IP forwarding disabled; missing NAT rule.
DNS not resolving DNS directive ignored (not using wg-quick); systemd-resolved conflict.
Periodic disconnects Missing PersistentKeepalive behind NAT.

Enable kernel debug logging:

echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control
dmesg -w | grep wireguard

Performance Considerations

WireGuard runs in kernel space and uses ChaCha20-Poly1305, which is optimised for processors without AES-NI. On modern x86-64 hardware expect line-rate throughput on gigabit links with negligible CPU overhead. For 10 Gbps links WireGuard routinely outperforms both OpenVPN (userspace) and IPsec (depending on configuration).


Return to the VPN and SSH hub for more guides, or continue with the OpenVPN Guide.