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
| Check | Command | Expected 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.