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 IPv6fdcc: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-toolsprovideswgandwg-quick.qrencodeis 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 ofiptables, the simple MASQUERADE rule above still works on Debian/Ubuntu becauseiptablesuses 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.confin 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
masqueradefor10.8.0.0/24out 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 = 1280on clients in[Interface]. - Ensure
AllowedIPsandDNSare set correctly on clients. - If the server is behind NAT, set
PersistentKeepalive = 25on 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.