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.