WireGuard server

Chapter 3 — WireGuard Server

WireGuard is built into the Linux kernel since version 5.6, which means on any modern Debian or Ubuntu machine there is nothing to compile — just install the userspace tools and configure the interface. By the end of this chapter you'll have a working VPN server that clients can connect to securely.

What We're Building

VPN tunnel (UDP 51820)                                               
client (10.0.0.2) ══════════════════════ server wg0 (10.0.0.1)
                                               public IP: your server / home IP
VPN subnet: 10.0.0.0/24    server eth0: your real public / LAN IP    port: UDP 51820

The server gets the first address in the VPN subnet (10.0.0.1). Each client gets its own address (10.0.0.2, 10.0.0.3, …). Traffic between clients and the server travels through the encrypted WireGuard tunnel. Optionally you can route all client internet traffic through the server too — useful when on untrusted Wi-Fi.

Step 1 — Install WireGuard

server — install WireGuard tools
# Debian 11+ / Ubuntu 20.04+ — WireGuard is in the standard repos root@server:~# apt update && apt install -y wireguard # confirm the kernel module is available root@server:~# modprobe wireguard && echo "module loaded" module loaded # check the installed tools root@server:~# wg --version wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/

Step 2 — Generate Key Pairs

WireGuard uses Curve25519 public/private key pairs for authentication. Each peer (server and every client) needs its own key pair. The private key never leaves the machine it was generated on.

server — generate server key pair
# work in /etc/wireguard — lock down permissions immediately root@server:~# cd /etc/wireguard root@server:/etc/wireguard# umask 077 # generate the server private key root@server:/etc/wireguard# wg genkey > server_private.key # derive the server public key from the private key root@server:/etc/wireguard# wg pubkey < server_private.key > server_public.key # generate the first client's key pair (do this for each client) root@server:/etc/wireguard# wg genkey > client1_private.key root@server:/etc/wireguard# wg pubkey < client1_private.key > client1_public.key # optional but recommended: preshared key for extra symmetric encryption root@server:/etc/wireguard# wg genpsk > client1_psk.key # view the keys — you'll paste these into config files root@server:/etc/wireguard# cat server_private.key qH8X2vK9mN3pL7rT4wY1cB6sF5nJ0dA8eI2oU9gM= ← server private (keep secret) root@server:/etc/wireguard# cat server_public.key P3mK9vX2nL7rH8T4wY1cB6sF5nJ0dA8eI2oU9gQ= ← share with clients
Private keys must stay private. Never paste a private key into a chat message, email, or web form. The key files in /etc/wireguard/ should be owned by root and readable only by root (chmod 600). Anyone with the server private key can impersonate your VPN server.

Step 3 — Write the Server Config (wg0.conf)

The server config has two sections: [Interface] defines this machine, and one [Peer] block per authorised client.

# /etc/wireguard/wg0.conf — server configuration [Interface] Address = 10.0.0.1/24 # server's VPN IP ListenPort = 51820 # UDP port clients connect to PrivateKey = qH8X2vK9mN3pL7rT... # paste server_private.key here # IP forwarding — needed if you want clients to route internet traffic through the server PostUp = iptables -A FORWARD -i %i -j ACCEPT; \ iptables -A FORWARD -o %i -j ACCEPT; \ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PreDown = iptables -D FORWARD -i %i -j ACCEPT; \ iptables -D FORWARD -o %i -j ACCEPT; \ iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # ── client 1 ────────────────────────────────────────────── [Peer] PublicKey = X7nK2vP9mL3rH8T4... # paste client1_public.key here PresharedKey = R5mT9vX2nL7rH8P4... # paste client1_psk.key (optional) AllowedIPs = 10.0.0.2/32 # only this IP is allowed from this peer # add more [Peer] blocks for each additional client # [Peer] # PublicKey = ...client2 public key... # AllowedIPs = 10.0.0.3/32
Replace eth0 with your actual interface name. On modern Debian/Ubuntu systems the external interface might be ens3, enp0s3, or ens18. Check with ip link show or ip route | grep default.

Step 4 — Enable IP Forwarding

If you want VPN clients to route their internet traffic through the server (full-tunnel mode), the server kernel needs IP forwarding enabled. Without this, the PostUp iptables rules won't route traffic onward:

server — enable IP forwarding
# enable immediately (until next reboot) root@server:~# sysctl -w net.ipv4.ip_forward=1 # make it permanent across reboots root@server:~# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf root@server:~# sysctl -p net.ipv4.ip_forward = 1 # verify root@server:~# cat /proc/sys/net/ipv4/ip_forward 1

Step 5 — Open the Firewall

server — UFW rule for WireGuard
root@server:~# ufw allow 51820/udp root@server:~# ufw status Status: active To Action From -- ------ ---- 51820/udp ALLOW Anywhere

Step 6 — Start WireGuard and Enable on Boot

server — bring up the interface
# bring the wg0 interface up now root@server:~# wg-quick up wg0 [#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip -4 address add 10.0.0.1/24 dev wg0 [#] ip link set mtu 1420 up dev wg0 [#] iptables -A FORWARD -i wg0 -j ACCEPT ... # enable as a systemd service — starts automatically on boot root@server:~# systemctl enable --now wg-quick@wg0 root@server:~# systemctl status wg-quick@wg0 ● wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0 Active: active (exited) since Thu 2026-06-18 10:22:11 BST # show current WireGuard status and peer list root@server:~# wg show interface: wg0 public key: P3mK9vX2nL7rH8T4wY1cB6sF5nJ0dA8eI2oU9gQ= private key: (hidden) listening port: 51820

Step 7 — Write the Client Config

Generate this on the server using the client's private key, then transfer it securely to the client device (QR code, encrypted file transfer, or physical access). Do not send it over plain email or chat.

# client1.conf — the config file given to the client device [Interface] Address = 10.0.0.2/32 # this client's VPN IP PrivateKey = Y2nL7rH8T4wX3vK9m... # client1_private.key DNS = 10.0.0.1 # use the VPN server as DNS resolver (optional) [Peer] PublicKey = P3mK9vX2nL7rH8T4... # server_public.key PresharedKey = R5mT9vX2nL7rH8P4... # client1_psk.key (if used) Endpoint = your.server.ip:51820 # server's public IP or hostname AllowedIPs = 0.0.0.0/0 # route ALL traffic through VPN (full tunnel) # AllowedIPs = 10.0.0.0/24 # OR: route only VPN subnet (split tunnel) PersistentKeepalive = 25 # keep tunnel alive through NAT (send keepalive every 25s)
AllowedIPs controls what gets routed through the tunnel. 0.0.0.0/0 is full-tunnel mode — all internet traffic goes through the VPN server. 10.0.0.0/24 is split-tunnel mode — only traffic destined for other VPN peers goes through the tunnel, and regular internet traffic continues directly. Chapter 8 covers split tunnelling in detail.

Generating a QR code for mobile clients

server — QR code for phone import
root@server:~# apt install -y qrencode root@server:~# qrencode -t ansiutf8 < /etc/wireguard/client1.conf [QR code appears in terminal — scan with WireGuard iOS/Android app]

Verifying the Connection

Once a client connects, wg show on the server reveals the handshake time and traffic counters — the definitive proof that the tunnel is working:

server — verify peer connection
root@server:~# wg show interface: wg0 public key: P3mK9vX2nL7rH8T4wY1cB6sF5nJ0dA8eI2oU9gQ= private key: (hidden) listening port: 51820 peer: X7nK2vP9mL3rH8T4wY1cB6sF5nJ0dA8eI2oU9gR= preshared key: (hidden) endpoint: 82.45.110.23:54321 allowed ips: 10.0.0.2/32 latest handshake: 14 seconds ago transfer: 1.44 MiB received, 248 KiB sent # ping the client from the server — confirms two-way tunnel root@server:~# ping -c 3 10.0.0.2 PING 10.0.0.2: 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=14.2 ms # from the client — verify all traffic routes through VPN (full tunnel) # your IP should now show as the VPN server's public IP user@client:~$ curl ifconfig.me 203.0.113.42 ← should match your server's public IP
CheckCommandExpected result
Interface up ip link show wg0 state UP
Peer handshake wg show latest handshake: N seconds ago
Ping server from client ping 10.0.0.1 replies received
Ping client from server ping 10.0.0.2 replies received
Public IP changed (full tunnel) curl ifconfig.me shows server's public IP
Service running on boot systemctl is-enabled wg-quick@wg0 enabled

Adding More Clients

server — add a second client without restarting
# generate keys for the new client root@server:/etc/wireguard# wg genkey > client2_private.key root@server:/etc/wireguard# wg pubkey < client2_private.key > client2_public.key # add the new peer to the RUNNING interface (no restart needed) root@server:~# wg set wg0 peer $(cat /etc/wireguard/client2_public.key) \ allowed-ips 10.0.0.3/32 # save the running config back to wg0.conf so it survives a reboot root@server:~# wg-quick save wg0
wg set + wg-quick save lets you add peers to a live WireGuard interface without dropping existing connections. The save command writes the current in-memory state back to wg0.conf, keeping the file and the running interface in sync.

Quick Reference — wg Commands

# show all interface and peer status wg show # bring interface up / down wg-quick up wg0 wg-quick down wg0 # reload config after editing wg0.conf (drops and re-adds peers) wg-quick down wg0 && wg-quick up wg0 # add a peer to a running interface wg set wg0 peer <PUBLIC_KEY> allowed-ips 10.0.0.X/32 # remove a peer from a running interface wg set wg0 peer <PUBLIC_KEY> remove # save running state back to config file wg-quick save wg0 # generate a new private key wg genkey # derive public key from private key wg pubkey < private.key # generate a preshared key wg genpsk
Next — Chapter 4: WireGuard Clients. The server is running — now connect to it. Chapter 4 covers WireGuard clients on Windows, macOS, iOS, and Android: installing the app, importing the config file (or scanning the QR code), enabling the kill switch, and verifying the tunnel from each platform.