WireGuard replaced OpenVPN as the default choice for self-hosted VPNs for good reasons: the codebase is roughly 4,000 lines (versus hundreds of thousands), it runs in the Linux kernel, handshakes complete in two round trips, and a properly tuned WireGuard tunnel pushes line-rate traffic on modest hardware. This guide gets you from a fresh VPS to a working tunnel with mobile and desktop clients in about ten minutes, then layers on the things that matter for daily use: QR code generation, firewall rules, and kill switch configuration on the client side.

What You Will Build

A WireGuard server on your VPS, three client configurations (one phone, one laptop, one extra slot), QR code provisioning for mobile, NAT rules for full-tunnel internet access through the VPN, and kill switch settings so client devices fail closed if the tunnel drops.

Prerequisites

  • A VPS running Debian 12 or Ubuntu 24.04
  • Root SSH access
  • A public IPv4 address (IPv6 supported too, optional here)
  • UDP port 51820 open in any upstream firewall

For most personal use cases, the entry-level Cloud VPS is more than enough. WireGuard traffic is light on CPU.

Step 1: Install WireGuard

apt update && apt upgrade -y
apt install -y wireguard wireguard-tools qrencode iptables-persistent

qrencode generates the QR codes for mobile clients. iptables-persistent keeps NAT rules across reboots.

Step 2: Enable IP Forwarding

echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard.conf
echo 'net.ipv6.conf.all.forwarding=1' >> /etc/sysctl.d/99-wireguard.conf
sysctl --system

Without this, the kernel will not forward packets between the WireGuard interface and the public NIC.

Step 3: Generate Server Keys

cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.key

umask 077 ensures the private key is only readable by root. Verify with ls -la.

Step 4: Identify the Outbound Interface

ip route | grep default

The output looks like default via 192.0.2.1 dev eth0. Note the interface name (eth0 here; on cloud VPS it is often ens3, enp1s0, or similar). You will use it in the next step for NAT.

Step 5: Write the Server Config

Create /etc/wireguard/wg0.conf:

[Interface]
Address = 10.66.66.1/24
ListenPort = 51820
PrivateKey = <contents of server_private.key>
SaveConfig = false

PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp   = iptables -A FORWARD -o wg0 -j ACCEPT
PostUp   = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Replace <contents of server_private.key> with the actual key (run cat server_private.key and paste). Replace eth0 with your interface name. Lock the file: chmod 600 wg0.conf.

The 10.66.66.0/24 range is arbitrary. Avoid 10.0.0.0/24 and 192.168.1.0/24 because they often collide with home networks on the client side.

Step 6: Start the Server

systemctl enable --now wg-quick@wg0
wg show

If wg show prints the interface name, public key, and listening port, the server is live.

Step 7: Generate the First Client

For each client, generate a unique keypair:

mkdir -p /etc/wireguard/clients
cd /etc/wireguard/clients
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
wg genpsk > phone_preshared.key

The pre-shared key adds an extra layer of symmetric encryption to mitigate any future quantum decryption of intercepted handshakes. It costs nothing and we always include it.

Step 8: Write the Client Config

Create /etc/wireguard/clients/phone.conf:

[Interface]
PrivateKey = <contents of phone_private.key>
Address = 10.66.66.2/32
DNS = 1.1.1.1, 9.9.9.9

[Peer]
PublicKey = <contents of server_public.key>
PresharedKey = <contents of phone_preshared.key>
Endpoint = vps.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

AllowedIPs = 0.0.0.0/0, ::/0 makes this a full-tunnel VPN: all traffic from the device goes through the VPS. To make it split-tunnel (only specific networks routed through), replace with something like AllowedIPs = 10.66.66.0/24, 192.168.50.0/24.

PersistentKeepalive = 25 sends a small heartbeat every 25 seconds. This keeps NAT mappings alive on the client's home router and is mandatory for mobile clients on cellular networks.

Step 9: Register the Client on the Server

Add a [Peer] block to /etc/wireguard/wg0.conf:

[Peer]
PublicKey = <contents of phone_public.key>
PresharedKey = <contents of phone_preshared.key>
AllowedIPs = 10.66.66.2/32

Reload the configuration without dropping the tunnel:

wg syncconf wg0 <(wg-quick strip wg0)

Verify with wg show. The new peer appears immediately.

Step 10: Generate a QR Code

For phones, scanning a QR code is faster and less error-prone than copying text:

qrencode -t ansiutf8 < /etc/wireguard/clients/phone.conf

The QR code prints directly in your terminal. Open the WireGuard app on your phone, tap "Add tunnel" then "Scan from QR code", point at the terminal, and the tunnel imports in one shot. Toggle it on and traffic flows through the VPS.

Important: clear the terminal afterward (clear then history -c) so the private key is not visible to anyone with future shell access.

Step 11: Add Desktop and Spare Clients

Repeat steps 7 through 9 for each additional client. Increment the IP address (10.66.66.3, 10.66.66.4, and so on). For laptops, copy the .conf file directly to the client and import it into the WireGuard desktop app.

A simple script to automate client creation:

#!/bin/bash
# /etc/wireguard/new-client.sh
NAME=$1
IP=$2
if [ -z "$NAME" ] || [ -z "$IP" ]; then
  echo "Usage: $0 <name> <ip_suffix>"
  exit 1
fi

cd /etc/wireguard/clients
umask 077
wg genkey | tee "${NAME}_private.key" | wg pubkey > "${NAME}_public.key"
wg genpsk > "${NAME}_preshared.key"

SERVER_PUBLIC=$(cat /etc/wireguard/server_public.key)
PRIV=$(cat "${NAME}_private.key")
PSK=$(cat "${NAME}_preshared.key")
PUB=$(cat "${NAME}_public.key")

cat > "${NAME}.conf" <<EOF
[Interface]
PrivateKey = ${PRIV}
Address = 10.66.66.${IP}/32
DNS = 1.1.1.1, 9.9.9.9

[Peer]
PublicKey = ${SERVER_PUBLIC}
PresharedKey = ${PSK}
Endpoint = vps.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

cat >> /etc/wireguard/wg0.conf <<EOF

[Peer]
# ${NAME}
PublicKey = ${PUB}
PresharedKey = ${PSK}
AllowedIPs = 10.66.66.${IP}/32
EOF

wg syncconf wg0 <(wg-quick strip wg0)
echo "Client ${NAME} created. QR code:"
qrencode -t ansiutf8 < "${NAME}.conf"

Run with ./new-client.sh laptop 3 and you get a fully wired client in seconds.

Firewall Rules

If you use UFW, allow WireGuard and SSH:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 51820/udp
ufw enable

UFW does not interfere with the MASQUERADE rule defined in the WireGuard PostUp because that rule operates in the nat table, not the filter table UFW manages.

Kill Switch Configuration on Clients

A kill switch blocks all internet traffic when the VPN drops, preventing IP leaks. Implementation differs per client.

Mobile (iOS and Android WireGuard app): Enable "On-Demand" (iOS) or "Auto-connect" (Android), then set the trigger to "Always on" or for the specific Wi-Fi networks you do not trust. Both apps block traffic when the tunnel is required but down.

Linux (NetworkManager or wg-quick): Add to the client config under [Interface]:

PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT

This rejects all traffic that does not exit through the WireGuard interface while the tunnel is active.

Windows (WireGuard for Windows): Edit the tunnel and tick "Block untunneled traffic (kill-switch)". The app handles the firewall rules automatically.

Testing the Tunnel

From a connected client:

  1. Visit https://ifconfig.me and confirm the IP matches your VPS
  2. Run a DNS leak test at https://dnsleaktest.com
  3. Check latency: ping 10.66.66.1 from the client should hit single-digit milliseconds on a nearby VPS

If the IP shows the VPS but DNS leaks to your ISP, your client config's DNS = line was not applied. Some operating systems require additional configuration to honor the VPN-provided DNS server.

FAQ

How many clients can one WireGuard server handle? On a 1 vCPU VPS, hundreds of concurrent peers and 500 Mbit of throughput are realistic. The bottleneck is usually the VPS network interface, not WireGuard itself.

Does WireGuard work behind CGNAT or restrictive corporate firewalls? Outbound UDP to port 51820 is required. If the client network blocks arbitrary UDP, you can change the server's ListenPort to 443 or 53, which most networks allow.

Can I run WireGuard alongside OpenVPN on the same VPS? Yes. They use different ports and kernel modules. Many users keep both as a fallback during migration.

Is WireGuard logging traffic? WireGuard itself logs nothing about traffic content. The server stores only peer public keys and the most recent handshake timestamp, visible via wg show.

What is the difference between full-tunnel and split-tunnel? Full-tunnel routes all client traffic through the VPN. Split-tunnel routes only specific networks. Full-tunnel is what you want for privacy or geo-locked content; split-tunnel is useful for accessing private services without affecting general internet use.

Where to Run It

WireGuard's CPU footprint is small, but throughput and stability depend on the network underneath. An EU VPS gives you predictable peering to most European networks and keeps your traffic within EU jurisdiction. Our Cloud VPS instances are appropriately sized for a personal or family VPN, with included backups so your peer list and configurations survive any accidental wipe.

Was this answer helpful? 0 Users Found This Useful (0 Votes)