Set up a secure WireGuard VPN server with automated client management, including key generation, firewall configuration, and traffic routing for remote access.
Prerequisites
- Root or sudo access
- Server with public IP address
- Basic command line knowledge
What this solves
WireGuard provides a modern, fast, and secure VPN solution that's easier to configure than traditional VPN protocols like OpenVPN or IPSec. This tutorial shows you how to set up a WireGuard server with proper client management, allowing secure remote access to your network or internet traffic routing through your server.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of WireGuard and dependencies.
sudo apt update && sudo apt upgrade -y
Install WireGuard
Install WireGuard and the necessary tools for key generation and management.
sudo apt install -y wireguard wireguard-tools
Generate server keys
Create the public and private key pair for your WireGuard server. These keys authenticate your server and encrypt traffic.
sudo mkdir -p /etc/wireguard
cd /etc/wireguard
sudo wg genkey | sudo tee server_private.key
sudo cat server_private.key | wg pubkey | sudo tee server_public.key
Set proper key permissions
Secure the private key by restricting access to root only. The private key must never be readable by other users.
sudo chmod 600 /etc/wireguard/server_private.key
sudo chmod 644 /etc/wireguard/server_public.key
Find your network interface
Identify your server's main network interface to configure NAT routing properly.
ip route | grep default
ip addr show
Look for the interface in the default route (usually eth0, ens3, or similar). Note this interface name for the next step.
Create WireGuard server configuration
Configure the WireGuard server with network settings, key authentication, and routing rules. Replace eth0 with your actual interface name.
[Interface]
PrivateKey = $(sudo cat /etc/wireguard/server_private.key)
Address = 10.0.0.1/24
ListenPort = 51820
SaveConfig = true
Enable IP forwarding and NAT
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Create the configuration file with proper substitution
Since we need to substitute the actual private key, create the configuration file with this command.
sudo bash -c 'cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = $(cat /etc/wireguard/server_private.key)
Address = 10.0.0.1/24
ListenPort = 51820
SaveConfig = true
Enable IP forwarding and NAT (replace eth0 with your interface)
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
EOF'
Enable IP forwarding
Configure the kernel to forward packets between network interfaces, which is required for VPN routing.
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Configure firewall
Open the WireGuard port and configure firewall rules for VPN traffic routing.
# Install ufw if not present
sudo apt install -y ufw
Configure UFW rules
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw --force enable
Start and enable WireGuard
Enable WireGuard to start automatically on boot and start the service now.
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo systemctl status wg-quick@wg0
Generate client keys
Create key pairs for your first client. Repeat this process for each client you want to add.
cd /etc/wireguard
sudo wg genkey | sudo tee client1_private.key
sudo cat client1_private.key | wg pubkey | sudo tee client1_public.key
sudo chmod 600 /etc/wireguard/client1_private.key
Add client to server configuration
Add the client's public key and assigned IP address to the server configuration.
sudo wg set wg0 peer $(sudo cat /etc/wireguard/client1_public.key) allowed-ips 10.0.0.2/32
sudo wg-quick save wg0
Create client configuration file
Generate a complete configuration file that clients can import. Replace 203.0.113.10 with your server's public IP address.
sudo bash -c 'cat > /etc/wireguard/client1.conf << EOF
[Interface]
PrivateKey = $(cat /etc/wireguard/client1_private.key)
Address = 10.0.0.2/32
DNS = 8.8.8.8, 8.8.4.4
[Peer]
PublicKey = $(cat /etc/wireguard/server_public.key)
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF'
Generate QR code for mobile clients
Install qrencode to generate QR codes that mobile WireGuard apps can scan for easy setup.
sudo apt install -y qrencode
sudo qrencode -t ansiutf8 < /etc/wireguard/client1.conf
Managing additional clients
Add a new client
Use this script template to easily add new clients with automatic IP assignment.
#!/bin/bash
CLIENT_NAME="client2"
CLIENT_IP="10.0.0.3"
SERVER_PUBLIC_IP="203.0.113.10"
Generate client keys
cd /etc/wireguard
sudo wg genkey | sudo tee ${CLIENT_NAME}_private.key
sudo cat ${CLIENT_NAME}_private.key | wg pubkey | sudo tee ${CLIENT_NAME}_public.key
sudo chmod 600 /etc/wireguard/${CLIENT_NAME}_private.key
Add to server
sudo wg set wg0 peer $(sudo cat /etc/wireguard/${CLIENT_NAME}_public.key) allowed-ips ${CLIENT_IP}/32
sudo wg-quick save wg0
Create client config
sudo bash -c "cat > /etc/wireguard/${CLIENT_NAME}.conf << EOF
[Interface]
PrivateKey = \$(cat /etc/wireguard/${CLIENT_NAME}_private.key)
Address = ${CLIENT_IP}/32
DNS = 8.8.8.8, 8.8.4.4
[Peer]
PublicKey = \$(cat /etc/wireguard/server_public.key)
Endpoint = ${SERVER_PUBLIC_IP}:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF"
Remove a client
Remove a client's access by removing their peer configuration from the server.
# Remove client from server
sudo wg set wg0 peer $(sudo cat /etc/wireguard/client1_public.key) remove
sudo wg-quick save wg0
Clean up client files
sudo rm /etc/wireguard/client1_private.key
sudo rm /etc/wireguard/client1_public.key
sudo rm /etc/wireguard/client1.conf
Verify your setup
# Check WireGuard status
sudo wg show
sudo systemctl status wg-quick@wg0
Check if IP forwarding is enabled
sysctl net.ipv4.ip_forward
Check firewall rules
sudo iptables -t nat -L POSTROUTING
sudo iptables -L FORWARD
Test connectivity (after connecting a client)
ping 10.0.0.2
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Connection timeouts | Firewall blocking port 51820 | Check firewall rules with sudo ufw status or sudo firewall-cmd --list-all |
| Can connect but no internet | IP forwarding disabled or wrong interface in PostUp | Check sysctl net.ipv4.ip_forward and verify interface name in wg0.conf |
| Handshake fails | Clock synchronization issues | Install and enable NTP: sudo apt install ntp or sudo dnf install chrony |
| Permission denied on keys | Incorrect file permissions | Set private keys to 600: sudo chmod 600 /etc/wireguard/*_private.key |
| Service won't start | Configuration syntax error | Check config with sudo wg-quick up wg0 for detailed error messages |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
WG_DIR="/etc/wireguard"
WG_CONF="$WG_DIR/wg0.conf"
WG_PORT="${WG_PORT:-51820}"
WG_NETWORK="${WG_NETWORK:-10.0.0.0/24}"
WG_SERVER_IP="${WG_SERVER_IP:-10.0.0.1}"
CLIENT_NAME="${1:-client1}"
# Error handling
cleanup() {
echo -e "${RED}[ERROR]${NC} Installation failed. Cleaning up..."
systemctl stop wg-quick@wg0 2>/dev/null || true
systemctl disable wg-quick@wg0 2>/dev/null || true
rm -f "$WG_CONF" 2>/dev/null || true
exit 1
}
trap cleanup ERR
usage() {
echo "Usage: $0 [CLIENT_NAME]"
echo " CLIENT_NAME: Name for the first client (default: client1)"
echo ""
echo "Environment variables:"
echo " WG_PORT: WireGuard listen port (default: 51820)"
echo " WG_NETWORK: VPN network CIDR (default: 10.0.0.0/24)"
echo " WG_SERVER_IP: Server IP in VPN network (default: 10.0.0.1)"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
exit 1
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root or with sudo"
fi
}
# Detect distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PKG_UPGRADE="apt upgrade -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
# Check if dnf exists, fallback to yum
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum update -y"
fi
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum update -y"
FIREWALL_CMD="firewall-cmd"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
else
error "Cannot detect distribution. /etc/os-release not found."
fi
log "Detected distribution: $ID"
}
# Install EPEL on RHEL-based systems if needed
install_epel() {
if [[ "$PKG_MGR" == "yum" ]] || [[ "$PKG_MGR" == "dnf" ]]; then
if ! rpm -qa | grep -q epel-release; then
log "Installing EPEL repository..."
$PKG_INSTALL epel-release
fi
fi
}
# Find default network interface
find_interface() {
DEFAULT_INTERFACE=$(ip route | grep '^default' | grep -o 'dev [^[:space:]]*' | head -1 | awk '{print $2}')
if [[ -z "$DEFAULT_INTERFACE" ]]; then
error "Could not determine default network interface"
fi
log "Using network interface: $DEFAULT_INTERFACE"
}
# Enable IP forwarding
enable_ip_forwarding() {
log "Enabling IP forwarding..."
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-wireguard.conf
sysctl -p /etc/sysctl.d/99-wireguard.conf
}
# Configure firewall
configure_firewall() {
log "Configuring firewall..."
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
# Ubuntu/Debian with ufw
if command -v ufw &> /dev/null; then
ufw allow "$WG_PORT"/udp
# Enable if not already enabled
if ! ufw status | grep -q "Status: active"; then
echo "y" | ufw enable
fi
fi
elif [[ "$FIREWALL_CMD" == "firewall-cmd" ]]; then
# RHEL-based with firewalld
if systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-port="$WG_PORT"/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload
fi
fi
}
# Generate server keys
generate_server_keys() {
log "Generating server keys..."
mkdir -p "$WG_DIR"
chmod 755 "$WG_DIR"
wg genkey | tee "$WG_DIR/server_private.key" | wg pubkey > "$WG_DIR/server_public.key"
chmod 600 "$WG_DIR/server_private.key"
chmod 644 "$WG_DIR/server_public.key"
}
# Create server configuration
create_server_config() {
log "Creating WireGuard server configuration..."
SERVER_PRIVATE_KEY=$(cat "$WG_DIR/server_private.key")
cat > "$WG_CONF" << EOF
[Interface]
PrivateKey = $SERVER_PRIVATE_KEY
Address = $WG_SERVER_IP/24
ListenPort = $WG_PORT
SaveConfig = false
# Enable IP forwarding and NAT
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o $DEFAULT_INTERFACE -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o $DEFAULT_INTERFACE -j MASQUERADE
EOF
chmod 600 "$WG_CONF"
}
# Generate client configuration
generate_client_config() {
log "Generating client configuration for: $CLIENT_NAME"
# Generate client keys
CLIENT_PRIVATE_KEY=$(wg genkey)
CLIENT_PUBLIC_KEY=$(echo "$CLIENT_PRIVATE_KEY" | wg pubkey)
SERVER_PUBLIC_KEY=$(cat "$WG_DIR/server_public.key")
# Get server public IP
SERVER_PUBLIC_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP")
# Create client config
cat > "$WG_DIR/${CLIENT_NAME}.conf" << EOF
[Interface]
PrivateKey = $CLIENT_PRIVATE_KEY
Address = 10.0.0.2/24
DNS = 8.8.8.8, 8.8.4.4
[Peer]
PublicKey = $SERVER_PUBLIC_KEY
Endpoint = $SERVER_PUBLIC_IP:$WG_PORT
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF
chmod 600 "$WG_DIR/${CLIENT_NAME}.conf"
# Add client to server config
cat >> "$WG_CONF" << EOF
[Peer]
PublicKey = $CLIENT_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32
EOF
}
# Start and enable WireGuard
start_wireguard() {
log "Starting and enabling WireGuard service..."
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
}
# Verify installation
verify_installation() {
log "Verifying WireGuard installation..."
if systemctl is-active --quiet wg-quick@wg0; then
log "WireGuard service is running"
else
error "WireGuard service is not running"
fi
if wg show | grep -q interface; then
log "WireGuard interface is active"
else
error "WireGuard interface is not active"
fi
}
# Main installation process
main() {
if [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then
usage
fi
echo "[1/11] Checking prerequisites..."
check_root
echo "[2/11] Detecting distribution..."
detect_distro
echo "[3/11] Installing EPEL repository (if needed)..."
install_epel
echo "[4/11] Updating system packages..."
$PKG_UPDATE
$PKG_UPGRADE
echo "[5/11] Installing WireGuard..."
$PKG_INSTALL wireguard-tools
echo "[6/11] Finding network interface..."
find_interface
echo "[7/11] Enabling IP forwarding..."
enable_ip_forwarding
echo "[8/11] Generating server keys..."
generate_server_keys
echo "[9/11] Creating server configuration..."
create_server_config
echo "[10/11] Generating client configuration..."
generate_client_config
echo "[11/11] Configuring firewall..."
configure_firewall
echo "[12/12] Starting WireGuard service..."
start_wireguard
echo "[13/13] Verifying installation..."
verify_installation
log "WireGuard VPN server installation completed successfully!"
log "Client configuration saved to: $WG_DIR/${CLIENT_NAME}.conf"
log "Server public key: $(cat $WG_DIR/server_public.key)"
warn "Please copy the client configuration file to your client device"
warn "To add more clients, use: wg genkey | tee client_private.key | wg pubkey > client_public.key"
}
main "$@"
Review the script before running. Execute with: bash install.sh