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 reposroot@server:~#apt update && apt install -y wireguard# confirm the kernel module is availableroot@server:~#modprobe wireguard && echo "module loaded"module loaded# check the installed toolsroot@server:~#wg --versionwireguard-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 immediatelyroot@server:~#cd /etc/wireguardroot@server:/etc/wireguard#umask 077# generate the server private keyroot@server:/etc/wireguard#wg genkey > server_private.key# derive the server public key from the private keyroot@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.keyroot@server:/etc/wireguard#wg pubkey < client1_private.key > client1_public.key# optional but recommended: preshared key for extra symmetric encryptionroot@server:/etc/wireguard#wg genpsk > client1_psk.key# view the keys — you'll paste these into config filesroot@server:/etc/wireguard#cat server_private.keyqH8X2vK9mN3pL7rT4wY1cB6sF5nJ0dA8eI2oU9gM= ← server private (keep secret)root@server:/etc/wireguard#cat server_public.keyP3mK9vX2nL7rH8T4wY1cB6sF5nJ0dA8eI2oU9gQ= ← 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 IPListenPort = 51820# UDP port clients connect toPrivateKey = qH8X2vK9mN3pL7rT...# paste server_private.key here# IP forwarding — needed if you want clients to route internet traffic through the serverPostUp = 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 herePresharedKey = 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 rebootsroot@server:~#echo "net.ipv4.ip_forward=1" >> /etc/sysctl.confroot@server:~#sysctl -pnet.ipv4.ip_forward = 1# verifyroot@server:~#cat /proc/sys/net/ipv4/ip_forward1
Step 5 — Open the Firewall
server — UFW rule for WireGuard
root@server:~#ufw allow 51820/udproot@server:~#ufw statusStatus: 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 nowroot@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 bootroot@server:~#systemctl enable --now wg-quick@wg0root@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 listroot@server:~#wg showinterface: 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 IPPrivateKey = Y2nL7rH8T4wX3vK9m...# client1_private.keyDNS = 10.0.0.1# use the VPN server as DNS resolver (optional)[Peer]PublicKey = P3mK9vX2nL7rH8T4...# server_public.keyPresharedKey = R5mT9vX2nL7rH8P4...# client1_psk.key (if used)Endpoint = your.server.ip:51820# server's public IP or hostnameAllowedIPs = 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 qrencoderoot@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 showinterface: 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 tunnelroot@server:~#ping -c 3 10.0.0.2PING 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 IPuser@client:~$curl ifconfig.me203.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 clientroot@server:/etc/wireguard#wg genkey > client2_private.keyroot@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 rebootroot@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.