WireGuard: A Clean, UI-less Installation & Configuration Guide (Server + Clients)

This post shows how to set up a production-ready WireGuard VPN on a Linux server (Debian/Ubuntu), with IPv4/IPv6 routing, NAT, DNS, and multiple clients — no Docker, no web UI. All commands are copy-pasteable.


0) What you’ll get

  • A lightweight VPN on port 51820/udp
  • Private VPN subnet 10.8.0.0/24 (and optional IPv6 fdcc:ad94:bacf:61a4::/64)
  • Systemd-managed service (wg-quick@wg0)
  • Proper IP forwarding & NAT (iptables/nftables)
  • Example client configs for Linux, macOS, Windows, iOS, Android
  • Minimal hardening and troubleshooting notes

1) Server prerequisites

  • A clean Debian 12/13 or Ubuntu 22.04+ VM with a public IP
  • SSH access as root (or sudo)
  • Open UDP/51820 in your cloud firewall
  • Server’s public IP: YOUR_SERVER_IP
  • (Optional) Domain: vpn.example.com (A record pointing to the server)

2) Install WireGuard tools

# Debian/Ubuntu
apt update
apt install -y wireguard wireguard-tools qrencode
wireguard-tools provides wg and wg-quick. qrencode is handy to show mobile configs as QR codes (still CLI).

3) Pick VPN subnets

# IPv4 VPN subnet
WG_IPV4="10.8.0.0/24"
WG_SERVER_IPv4="10.8.0.1"

# Optional IPv6 ULA (RFC4193). Use your own prefix.
WG_IPV6="fdcc:ad94:bacf:61a4::/64"
WG_SERVER_IPv6="fdcc:ad94:bacf:61a4::1"

4) Generate server keys

umask 077
mkdir -p /etc/wireguard && cd /etc/wireguard
wg genkey | tee server_private.key | wg pubkey > server_public.key
SERVER_PRIV=$(cat server_private.key)
SERVER_PUB=$(cat server_public.key)

5) Create /etc/wireguard/wg0.conf

Replace YOUR_SERVER_IP (or your domain). If you don’t want IPv6, remove the Address = ...::1/64 line and the IPv6 NAT rules later.

cat >/etc/wireguard/wg0.conf <<'EOF'
[Interface]
# VPN addresses
Address = 10.8.0.1/24, fdcc:ad94:bacf:61a4::1/64
# Listen on UDP/51820
ListenPort = 51820
# Server private key
PrivateKey = REPLACE_SERVER_PRIVATE_KEY
# Save dynamic changes (optional)
SaveConfig = true

# NAT for IPv4 (iptables) – will be applied via PostUp/PostDown hooks
PostUp   = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o $(ip -4 route get 1.1.1.1 | awk '{print $5}') -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o $(ip -4 route get 1.1.1.1 | awk '{print $5}') -j MASQUERADE

# NAT for IPv6 (nftables or ip6tables). If you don't use IPv6, remove these lines.
PostUp   = sysctl -w net.ipv6.conf.all.forwarding=1
PostDown = true

# Example peer stubs (add real ones later)
# [Peer]
# PublicKey = PEER1_PUBLIC_KEY
# AllowedIPs = 10.8.0.10/32, fdcc:ad94:bacf:61a4::10/128
EOF

Insert the server private key:

sed -i "s|REPLACE_SERVER_PRIVATE_KEY|$SERVER_PRIV|" /etc/wireguard/wg0.conf

6) Enable IP forwarding (kernel)

# IPv4 forwarding
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' >/etc/sysctl.d/99-wireguard-ipforward.conf

# Optional IPv6 forwarding (if you use IPv6 inside the tunnel)
sysctl -w net.ipv6.conf.all.forwarding=1
echo 'net.ipv6.conf.all.forwarding=1' >>/etc/sysctl.d/99-wireguard-ipforward.conf

# Persist
sysctl --system
If your distro defaults to nftables instead of iptables, the simple MASQUERADE rule above still works on Debian/Ubuntu because iptables uses the nft backend. If you maintain your own nftables ruleset, add an equivalent NAT rule there.

7) Start the VPN

# Start now
wg-quick up wg0

# Enable at boot
systemctl enable wg-quick@wg0

# Check status
wg show
ip a show wg0

You should see wg0 with 10.8.0.1/24 (and IPv6 if enabled).


8) Add a client (Peer) — the manual way

8.1 Generate client keys (on the server or the client)

cd /etc/wireguard
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
CLIENT_PRIV=$(cat client1_private.key)
CLIENT_PUB=$(cat client1_public.key)

8.2 Assign client addresses

Pick a unique IP per client:

Client1 IPv4: 10.8.0.10/32
Client1 IPv6: fdcc:ad94:bacf:61a4::10/128   # optional

8.3 Add the peer to the server

wg set wg0 peer "$CLIENT_PUB" allowed-ips 10.8.0.10/32,fdcc:ad94:bacf:61a4::10/128
wg-quick save wg0   # persists to wg0.conf if SaveConfig=true
wg show

This will append a [Peer] block to /etc/wireguard/wg0.conf.


9) Create the client config

Replace YOUR_SERVER_IP (or your domain). If the client is behind NAT, set PersistentKeepalive = 25.

cat > /etc/wireguard/client1.conf <<EOF
[Interface]
PrivateKey = $CLIENT_PRIV
Address = 10.8.0.10/32, fdcc:ad94:bacf:61a4::10/128
DNS = 1.1.1.1

[Peer]
PublicKey = $SERVER_PUB
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

Show as QR (for iOS/Android WireGuard app):

qrencode -t ansiutf8 < /etc/wireguard/client1.conf

Or scp the file to your laptop:

scp /etc/wireguard/client1.conf user@laptop:~/client1.conf

10) Client installation & usage

Linux (Debian/Ubuntu/Arch)

# Install once
sudo apt install -y wireguard wireguard-tools

# Put config at /etc/wireguard/wg0.conf
sudo mv ~/client1.conf /etc/wireguard/wg0.conf
sudo chmod 600 /etc/wireguard/wg0.conf

# Up / down
sudo wg-quick up wg0
sudo wg-quick down wg0
sudo systemctl enable wg-quick@wg0  # start at boot (optional)

macOS

  • Install the WireGuard app from App Store or brew install --cask wireguard-tools.
  • Import client1.conf in the app, toggle Activate.

Windows

  • Download WireGuard for Windows from the official site.
  • “Add Tunnel” → “Import from file” → select client1.conf → Activate.

iOS / Android

  • Install WireGuard app.
  • Scan the QR code or import client1.conf.
  • Toggle the tunnel ON.

11) Add more peers quickly (helper script)

Create a tiny helper on the server:

cat >/usr/local/bin/wg-add-peer <<'EOS'
#!/usr/bin/env bash
set -euo pipefail
NAME="${1:-client$(date +%s)}"
IPv4_LAST="${2:-$(shuf -i 10-250 -n 1)}"
IPv6_LAST="${3:-$(shuf -i 10-65000 -n 1)}"

cd /etc/wireguard
umask 077
wg genkey | tee ${NAME}_private.key | wg pubkey > ${NAME}_public.key
PRIV=$(cat ${NAME}_private.key)
PUB=$(cat ${NAME}_public.key)
SERVER_PUB=$(cat server_public.key)

cat > ${NAME}.conf <<EOF
[Interface]
PrivateKey = ${PRIV}
Address = 10.8.0.${IPv4_LAST}/32, fdcc:ad94:bacf:61a4::${IPv6_LAST}/128
DNS = 1.1.1.1

[Peer]
PublicKey = ${SERVER_PUB}
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

wg set wg0 peer "${PUB}" allowed-ips "10.8.0.${IPv4_LAST}/32,fdcc:ad94:bacf:61a4::${IPv6_LAST}/128"
wg-quick save wg0

echo "Created peer ${NAME}:"
echo "  IPv4: 10.8.0.${IPv4_LAST}   IPv6: fdcc:ad94:bacf:61a4::${IPv6_LAST}"
echo
echo "Config: /etc/wireguard/${NAME}.conf"
echo "QR:"
qrencode -t ansiutf8 < /etc/wireguard/${NAME}.conf
EOS

chmod +x /usr/local/bin/wg-add-peer

Usage:

wg-add-peer alice 10 10
wg-add-peer bob      # random IPs

12) Optional: restrict a client to only reach the VPN subnet

If you don’t want full-tunnel, change the client AllowedIPs:

AllowedIPs = 10.8.0.0/24, fdcc:ad94:bacf:61a4::/64

This makes it split-tunnel (only traffic to VPN subnets goes through WireGuard).


13) Firewall notes

If using UFW:

ufw allow 51820/udp
# Allow forwarding between wg0 and your main NIC (e.g., eth0)
ufw route allow in on wg0 out on eth0
ufw route allow in on eth0 out on wg0
ufw status

If using nftables with a custom ruleset, add:

  • Accept input udp dport 51820
  • Enable masquerade for 10.8.0.0/24 out via your WAN interface

14) MTU & “I can connect but can’t browse”

  • Start with default MTU (WireGuard auto-detects).
  • On very restrictive networks, try MTU = 1280 on clients in [Interface].
  • Ensure AllowedIPs and DNS are set correctly on clients.
  • If the server is behind NAT, set PersistentKeepalive = 25 on clients.

15) Rotate or revoke a client

  • To rotate keys, generate new keys for that client and replace them server-side.

To revoke, remove its [Peer] from the server:

wg set wg0 peer CLIENT_PUBLIC_KEY remove
wg-quick save wg0

16) Basic operations & inspection

# Show peers, endpoints, handshakes
wg show

# Live logs
journalctl -u wg-quick@wg0 -f

# Restart
systemctl restart wg-quick@wg0

17) Security checklist

  • Keep /etc/wireguard/* chmod 600, root-only.
  • Don’t reuse keys across clients.
  • Limit who can read client configs (they contain private keys).
  • If you expose SSH only via VPN, add server-side firewall rules after VPN works to avoid lockout.

18) Uninstall (if you ever need to)

systemctl disable --now wg-quick@wg0
rm -f /etc/wireguard/wg0.conf
apt purge -y wireguard wireguard-tools
rm -rf /etc/wireguard

Appendix: Minimal reference configs

Server (/etc/wireguard/wg0.conf)

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
PostUp   = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o $(ip -4 route get 1.1.1.1 | awk '{print $5}') -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o $(ip -4 route get 1.1.1.1 | awk '{print $5}') -j MASQUERADE

Client (full-tunnel)

[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = 10.8.0.10/32
DNS = 1.1.1.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

That’s it. You now have a lean, auditable, and fast WireGuard setup — fully managed from the command line, ready for real work.