WireGuard VPN on VPS
How to install and configure WireGuard to create a personal VPN on your Linux VPS
WireGuard is a modern, fast, and simple-to-configure VPN. Ideal for protecting your browsing, securely accessing your server, or creating a private network between multiple servers.
Looking to protect your WireGuard server from UDP flood attacks? See the DDoS Protection section at the bottom of this page.
Installation
# Ubuntu 20.04+
sudo apt update && sudo apt install wireguard -y
# CentOS/AlmaLinux
sudo dnf install wireguard-tools -yVPN Server Configuration
1. Generate Keys
# Generate server private and public key
wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key
sudo chmod 600 /etc/wireguard/server_private.key
# Display the keys (you'll need them later)
echo "Private: $(sudo cat /etc/wireguard/server_private.key)"
echo "Public: $(sudo cat /etc/wireguard/server_public.key)"2. Create Server Configuration
sudo nano /etc/wireguard/wg0.conf[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# Enable NAT (forward packets to internet)
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Replace eth0 with your network interface (ip a to see it)3. Enable IP Forwarding
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p4. Start WireGuard
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
# Verify
sudo wg show5. Open the Port in the Firewall
sudo ufw allow 51820/udpAdding a Client (Peer)
On Client: Generate Keys
# Linux/Mac
wg genkey | tee client_private.key | wg pubkey > client_public.key
cat client_private.key
cat client_public.key
# Windows: download WireGuard from wireguard.com, use "Add Tunnel" > "Create from scratch"On Server: Add the Peer
sudo nano /etc/wireguard/wg0.confAdd at the end:
[Peer]
# Client 1 (e.g., your PC)
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32# Reload the configuration without interrupting connections
sudo wg syncconf wg0 <(sudo wg-quick strip wg0)
# or
sudo systemctl restart wg-quick@wg0On Client: Create Configuration
[Interface]
Address = 10.0.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <VPS_IP>:51820
AllowedIPs = 0.0.0.0/0 # all traffic through VPN
# Or only traffic to the private network:
# AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25Adding Multiple Clients
Each client has a different IP in the VPN subnet:
# Client 2 (phone)
[Peer]
PublicKey = <PHONE_PUBLIC_KEY>
AllowedIPs = 10.0.0.3/32
# Client 3 (work laptop)
[Peer]
PublicKey = <LAPTOP_PUBLIC_KEY>
AllowedIPs = 10.0.0.4/32QR Code for Mobile Devices
Install qrencode to generate a QR code to scan with the WireGuard app on iOS/Android:
sudo apt install qrencode -y
# Generate QR from client config
qrencode -t ansiutf8 < /path/to/client.confManagement Commands
# Connection status
sudo wg show
# Show data transfer
sudo wg show wg0 transfer
# List connected peers
sudo wg show wg0 peers
# Add peer on the fly (without editing the file)
sudo wg set wg0 peer <pubkey> allowed-ips 10.0.0.5/32
# Remove peer
sudo wg set wg0 peer <pubkey> removeAutomatic Setup Script (All-in-One)
To quickly add a new client:
#!/bin/bash
# Usage: ./add-client.sh clientname
CLIENT=$1
CLIENT_IP="10.0.0.$(( $(sudo wg show wg0 peers | wc -l) + 2 ))"
wg genkey | tee /etc/wireguard/clients/${CLIENT}_private.key | wg pubkey > /etc/wireguard/clients/${CLIENT}_public.key
CLIENT_PRIV=$(cat /etc/wireguard/clients/${CLIENT}_private.key)
CLIENT_PUB=$(cat /etc/wireguard/clients/${CLIENT}_public.key)
SERVER_PUB=$(cat /etc/wireguard/server_public.key)
SERVER_IP=$(curl -s ifconfig.me)
# Add to server
echo -e "\n[Peer]\n# $CLIENT\nPublicKey = $CLIENT_PUB\nAllowedIPs = $CLIENT_IP/32" | sudo tee -a /etc/wireguard/wg0.conf
sudo wg syncconf wg0 <(sudo wg-quick strip wg0)
# Create client config
cat > /etc/wireguard/clients/${CLIENT}.conf << EOF
[Interface]
Address = $CLIENT_IP/24
PrivateKey = $CLIENT_PRIV
DNS = 1.1.1.1
[Peer]
PublicKey = $SERVER_PUB
Endpoint = $SERVER_IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF
echo "Client $CLIENT created: IP $CLIENT_IP"
cat /etc/wireguard/clients/${CLIENT}.confDDoS Protection
WireGuard has strong built-in DDoS resistance: every packet must pass a BLAKE2s MAC1 cryptographic check before WireGuard responds. An attacker who doesn't know your public key generates traffic that WireGuard silently discards at near-zero CPU cost.
However, a high-volume UDP flood can still saturate your port at the kernel level before WireGuard processes anything. A BPF (Berkeley Packet Filter) iptables rule mitigates this by dropping packets that don't match the WireGuard handshake initiation signature before they reach userspace.
WireGuard packet structure
WireGuard message types are identified by the first 4 bytes:
| Bytes 0-3 | Message type |
|---|---|
0x01 0x00 0x00 0x00 | Handshake Initiation |
0x02 0x00 0x00 0x00 | Handshake Response |
0x03 0x00 0x00 0x00 | Cookie Reply |
0x04 0x00 0x00 0x00 | Transport Data |
For NEW connections, only 0x01 (Handshake Initiation) is valid. All other packets on new connections are junk.
Apply the filter
Install iptables-persistent first:
sudo apt install iptables-persistent -yApply the three rules (replace 51820 if you use a different port):
# 1. Drop all new UDP connections on the WireGuard port by default
sudo iptables -I INPUT -p udp --dport 51820 -m state --state NEW -j DROP
# 2. Allow established WireGuard sessions through
sudo iptables -I INPUT -p udp --dport 51820 -m state --state ESTABLISHED,RELATED -j ACCEPT
# 3. Allow only packets starting with 0x01 (Handshake Initiation)
sudo iptables -I INPUT -p udp --dport 51820 -m state --state NEW \
-m bpf --bytecode "6,0 0 0 0,48 0 0 8,21 0 2 1,48 0 0 9,21 0 0 0,6 0 0 65535,6 0 0 0" \
-j ACCEPTSave rules:
sudo netfilter-persistent saveVerify rule order
sudo iptables -L INPUT -n --line-numbers | grep 51820Expected order:
1 ACCEPT udp anywhere anywhere udp dpt:51820 state NEW bpf ...
2 ACCEPT udp anywhere anywhere udp dpt:51820 state ESTABLISHED,RELATED
3 DROP udp anywhere anywhere udp dpt:51820 state NEWThis filter only helps against application-layer UDP floods. If the attack saturates your server's bandwidth before packets reach iptables, contact your provider for upstream null-routing or DDoS scrubbing.
Remove the filter
sudo iptables -D INPUT -p udp --dport 51820 -m state --state NEW -j DROP
sudo iptables -D INPUT -p udp --dport 51820 -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -L INPUT -n --line-numbers | grep bpf
sudo iptables -D INPUT <line-number>
sudo netfilter-persistent save