Network & Connectivity

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 -y

VPN 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 -p

4. Start WireGuard

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

# Verify
sudo wg show

5. Open the Port in the Firewall

sudo ufw allow 51820/udp

Adding 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.conf

Add 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@wg0

On 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 = 25

Adding 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/32

QR 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.conf

Management 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> remove

Automatic 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}.conf

DDoS 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-3Message type
0x01 0x00 0x00 0x00Handshake Initiation
0x02 0x00 0x00 0x00Handshake Response
0x03 0x00 0x00 0x00Cookie Reply
0x04 0x00 0x00 0x00Transport 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 -y

Apply 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 ACCEPT

Save rules:

sudo netfilter-persistent save

Verify rule order

sudo iptables -L INPUT -n --line-numbers | grep 51820

Expected 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 NEW

This 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

On this page