What is WireGuard and why not just use OpenVPN?

WireGuard was written by Jason Donenfeld and published in 2015. It runs in kernel space rather than user space, which is why it outperforms OpenVPN on the same hardware. On a Raspberry Pi 4 you can expect 200–300 Mbit/s throughput with WireGuard versus roughly 50–80 Mbit/s with OpenVPN.

The cryptography is opinionated by design. WireGuard uses Curve25519 for key exchange, ChaCha20-Poly1305 for encryption and BLAKE2s for hashing. OpenVPN lets you configure which algorithms to use, which sounds flexible but means people regularly misconfigure it with weaker settings. WireGuard has no weak configuration options. There is nothing to get wrong.

The codebase is small enough that a single person can audit it. That is a feature, not an accident.

⚠️ Only run this on infrastructure you own. Setting up a VPN server on your own Raspberry Pi on your own network is legal and sensible. Using WireGuard to access other people's systems without permission is a criminal offence under most jurisdictions. This guide covers a server on your own network.

Prerequisites

Check your kernel version:

uname -r
# Should return 5.15 or higher

Installation

sudo apt update && sudo apt install -y wireguard

That is the entire installation. Because WireGuard is in the kernel, this command only installs the user-space tools: wg and wg-quick. No compilation, no DKMS, no kernel modules to build.

wg --version
# e.g.: wireguard-tools v1.0.20210914

Generate keys

WireGuard uses static Curve25519 key pairs for authentication. Each peer (server and client) has its own pair.

# Generate the server's key pair
wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

# Lock down the private key
sudo chmod 600 /etc/wireguard/server_private.key

# Generate a client key pair (repeat for each new client)
wg genkey | tee client1_private.key | wg pubkey > client1_public.key

# Display all four keys — you will need them below
sudo cat /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_public.key
cat client1_private.key
cat client1_public.key
📝 Note: Private keys never leave the device they were generated on. The server's public key goes into the client config. The client's public key goes into the server config.

Server configuration

Create /etc/wireguard/wg0.conf. Replace SERVER_PRIVATE_KEY with the contents of /etc/wireguard/server_private.key, and CLIENT1_PUBLIC_KEY with the contents of client1_public.key.

sudo nano /etc/wireguard/wg0.conf
[Interface]
# Server's private key
PrivateKey = SERVER_PRIVATE_KEY

# Server's IP address inside the tunnel
Address = 10.0.0.1/24

# UDP port WireGuard listens on
ListenPort = 51820

# iptables rules applied on interface up/down
# Replace eth0 with your interface — check with: ip link show
PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# Client 1
PublicKey  = CLIENT1_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32
sudo chmod 600 /etc/wireguard/wg0.conf
💡 Tip: Find your network interface with ip link show. On Pi OS it is typically eth0 for wired and wlan0 for WiFi. Replace eth0 in the PostUp/PostDown lines accordingly.

Enable IP forwarding

The Pi needs to route traffic onwards rather than stop it. Open /etc/sysctl.conf and find this line:

#net.ipv4.ip_forward=1

Remove the #. Apply without rebooting:

sudo sysctl -p

# Verify — should return 1
cat /proc/sys/net/ipv4/ip_forward

Start and enable WireGuard

# Bring up wg0
sudo wg-quick up wg0

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

# Check what is running
sudo wg show

wg show displays the active interface, listening port, and any configured peers. If you see interface: wg0 with your ListenPort, the server is running. The peer section will show no handshake timestamp yet. That appears once a client connects.

Client configuration

WireGuard clients for Windows, macOS, iOS and Android all use the same config format. Download the official app from wireguard.com/install. On Linux: sudo apt install wireguard.

Create wg0-client1.conf on the client device:

[Interface]
# Client's private key (contents of client1_private.key)
PrivateKey = CLIENT1_PRIVATE_KEY

# Client's IP inside the tunnel — must match AllowedIPs on the server
Address = 10.0.0.2/32

# Optional: use the VPN server as DNS
DNS = 10.0.0.1

[Peer]
# Server's public key
PublicKey = SERVER_PUBLIC_KEY

# Server's public IP or DDNS hostname
Endpoint = YOUR_IP_OR_DDNS:51820

# 0.0.0.0/0 = full tunnel (all traffic via VPN)
# Use 10.0.0.0/24, 192.168.1.0/24 for split tunnel instead
AllowedIPs = 0.0.0.0/0, ::/0

# Keep the connection alive behind NAT
PersistentKeepalive = 25
📝 Full tunnel vs split tunnel: AllowedIPs = 0.0.0.0/0 routes all internet traffic through your Pi. If you only want access to your home network, use AllowedIPs = 10.0.0.0/24, 192.168.1.0/24 instead. Split tunnel is faster but leaves other traffic outside the VPN.

Add the client to a running server

You do not need to restart WireGuard to add a new peer:

sudo wg set wg0 peer CLIENT1_PUBLIC_KEY allowed-ips 10.0.0.2/32

To make it persist across reboots, save back to wg0.conf:

sudo wg-quick save wg0

Verify the connection

# On the client: bring up the tunnel
sudo wg-quick up wg0-client1

# Ping the server
ping 10.0.0.1

# On the server: check peers and handshake timestamp
sudo wg show

A latest handshake timestamp a few seconds old in wg show means the tunnel is up and encrypted traffic is flowing.

Troubleshooting

Port forwarding

This is the most common issue. Log into your router and forward UDP 51820 to the Pi's LAN IP. Test from outside your network (use mobile data, not your home WiFi):

nc -zvu YOUR_PUBLIC_IP 51820

UFW blocking traffic

If you are running UFW on the Pi:

sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable

# Allow forwarding in UFW
sudo nano /etc/default/ufw
# Set: DEFAULT_FORWARD_POLICY="ACCEPT"

MTU issues

PPPoE connections (common on older xDSL lines) have an MTU of 1492 instead of 1500. WireGuard defaults to 1420. If you see dropped connections or large file transfers failing, add this to [Interface] in the server config:

MTU = 1380

View logs

sudo journalctl -u wg-quick@wg0 -f

Adding more clients

Generate a fresh key pair for each client. Add a new [Peer] block to the server config with that client's public key and a unique tunnel IP (10.0.0.3/32, 10.0.0.4/32 and so on). The client config follows the same format shown above.

sudo wg show
Quick reference: Server port: UDP 51820 · Subnet: 10.0.0.0/24 · Server VPN IP: 10.0.0.1 · Client IPs: 10.0.0.2/32, 10.0.0.3/32 ... · Config: /etc/wireguard/wg0.conf

Frequently asked questions

What is the difference between WireGuard and OpenVPN?

WireGuard is faster, uses fewer resources and has a codebase of roughly 4,000 lines versus OpenVPN's ~100,000. Fewer lines means fewer potential vulnerabilities. OpenVPN is older and more battle-tested in enterprise environments. For a homelab, WireGuard wins on every measure.

Can I run WireGuard and Pi-hole on the same Pi?

Yes. Set Pi-hole's LAN IP as the DNS value in your WireGuard client config. All DNS traffic through the VPN then goes through Pi-hole, including from your phone when you are on mobile data.

Does WireGuard work behind CG-NAT?

CG-NAT is a problem because you have no static public IP. Two options: a cheap VPS as a jumphost (Hetzner CAX11 for ~4 EUR/month), or use Cloudflare Tunnel which requires no open port at all.

Sources