Create automated scripts to provision WireGuard clients, manage configurations, and monitor VPN connections. This tutorial builds comprehensive management tools for production WireGuard deployments.
Prerequisites
- WireGuard server already installed and configured
- Root or sudo access
- Basic knowledge of WireGuard configuration
- Understanding of JSON and bash scripting
What this solves
Managing WireGuard clients manually becomes cumbersome as your VPN infrastructure grows. This tutorial creates automated scripts for client provisioning, configuration management, and monitoring to streamline WireGuard operations. You'll build tools that generate client configurations, distribute keys securely, and track connection health automatically.
Step-by-step configuration
Verify WireGuard server installation
Ensure your WireGuard server is properly configured before setting up automation scripts. This confirms the base infrastructure is working correctly.
sudo systemctl status wg-quick@wg0
sudo wg show
Install required dependencies
Install tools needed for automated client management including QR code generation and JSON processing utilities.
sudo apt update
sudo apt install -y qrencode jq curl wireguard-tools
sudo dnf install -y epel-release
sudo dnf install -y qrencode jq curl wireguard-tools
Create management directory structure
Set up organized directories for scripts, client configurations, and logs with proper permissions.
sudo mkdir -p /opt/wireguard-manager/{scripts,clients,logs,templates}
sudo mkdir -p /opt/wireguard-manager/clients/{active,revoked,configs}
sudo chown -R root:root /opt/wireguard-manager
sudo chmod -R 755 /opt/wireguard-manager
sudo chmod 700 /opt/wireguard-manager/clients
Create client database file
Initialize a JSON database to track client information, IP assignments, and connection status.
{
"clients": [],
"next_ip": 2,
"subnet": "10.0.0.0/24",
"server_public_key": "",
"server_endpoint": "",
"dns_servers": ["1.1.1.1", "8.8.8.8"]
}
sudo chmod 600 /opt/wireguard-manager/clients/database.json
Create client configuration template
Define a reusable template for generating client configurations with proper security settings.
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = CLIENT_IP/32
DNS = DNS_SERVERS
[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = SERVER_ENDPOINT
PersistentKeepalive = 25
Create client provisioning script
Build the main script that provisions new clients, generates keys, and updates server configuration automatically.
#!/bin/bash
# WireGuard Client Provisioning Script
# Usage: ./provision-client.sh
set -euo pipefail
# Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
SERVER_CONFIG="/etc/wireguard/wg0.conf"
TEMPLATE="$WG_DIR/templates/client.conf"
LOG_FILE="$WG_DIR/logs/provisioning.log"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Validate input
if [[ $# -lt 1 ]]; then
echo "Usage: $0
sudo chmod 755 /opt/wireguard-manager/scripts/provision-client.sh
Create client revocation script
Build a script to safely revoke client access by removing their configuration and updating the server.
#!/bin/bash
# WireGuard Client Revocation Script
# Usage: ./revoke-client.sh
set -euo pipefail
# Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
SERVER_CONFIG="/etc/wireguard/wg0.conf"
LOG_FILE="$WG_DIR/logs/provisioning.log"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Validate input
if [[ $# -ne 1 ]]; then
echo "Usage: $0
sudo chmod 755 /opt/wireguard-manager/scripts/revoke-client.sh
Create client monitoring script
Build a monitoring script that checks client connection status and updates the database with connection information.
#!/bin/bash
# WireGuard Client Monitoring Script
# Updates client connection status and last seen timestamps
set -euo pipefail
# Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
LOG_FILE="$WG_DIR/logs/monitoring.log"
STATS_FILE="$WG_DIR/logs/connection-stats.json"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Get current WireGuard status
WG_STATUS=$(sudo wg show wg0 dump 2>/dev/null || echo "")
if [[ -z "$WG_STATUS" ]]; then
log "WARNING: Cannot retrieve WireGuard status"
exit 1
fi
# Initialize stats
STATS=$(cat <
sudo chmod 755 /opt/wireguard-manager/scripts/monitor-clients.sh
Create client listing script
Build a utility script to display client information in a readable format for administrative purposes.
#!/bin/bash
# WireGuard Client Listing Script
# Displays client information in various formats
set -euo pipefail
# Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
# Parse arguments
SHOW_ALL=false
SHOW_ACTIVE=false
SHOW_REVOKED=false
OUTPUT_FORMAT="table"
while [[ $# -gt 0 ]]; do
case $1 in
--all)
SHOW_ALL=true
shift
;;
--active)
SHOW_ACTIVE=true
shift
;;
--revoked)
SHOW_REVOKED=true
shift
;;
--json)
OUTPUT_FORMAT="json"
shift
;;
--help)
echo "Usage: $0 [--all|--active|--revoked] [--json]"
echo " --all Show all clients (default)"
echo " --active Show only active clients"
echo " --revoked Show only revoked clients"
echo " --json Output in JSON format"
exit 0
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Default to show all if no filter specified
if [[ "$SHOW_ACTIVE" == "false" && "$SHOW_REVOKED" == "false" ]]; then
SHOW_ALL=true
fi
# Build jq filter
JQ_FILTER=".clients[]"
if [[ "$SHOW_ACTIVE" == "true" ]]; then
JQ_FILTER="$JQ_FILTER | select(.status == \"active\")"
elif [[ "$SHOW_REVOKED" == "true" ]]; then
JQ_FILTER="$JQ_FILTER | select(.status == \"revoked\")"
fi
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
jq "[$JQ_FILTER]" "$CLIENT_DB"
else
echo "WireGuard Client Status Report"
echo "Generated: $(date)"
echo ""
printf "%-20s %-15s %-12s %-20s %-20s\n" "Name" "IP Address" "Status" "Created" "Last Seen"
printf "%-20s %-15s %-12s %-20s %-20s\n" "----" "----------" "------" "-------" "---------"
while IFS= read -r client; do
if [[ "$client" == "null" ]]; then
continue
fi
NAME=$(echo "$client" | jq -r '.name')
IP=$(echo "$client" | jq -r '.ip')
STATUS=$(echo "$client" | jq -r '.status')
CREATED=$(echo "$client" | jq -r '.created' | cut -d'T' -f1)
LAST_SEEN=$(echo "$client" | jq -r '.last_seen // "Never"' | cut -d'T' -f1)
printf "%-20s %-15s %-12s %-20s %-20s\n" "$NAME" "$IP" "$STATUS" "$CREATED" "$LAST_SEEN"
done < <(jq -c "$JQ_FILTER" "$CLIENT_DB")
fi
sudo chmod 755 /opt/wireguard-manager/scripts/list-clients.sh
Set up automated monitoring with cron
Configure cron to run client monitoring automatically and rotate logs to maintain system health.
sudo crontab -e
Add these lines to run monitoring every 5 minutes and daily log rotation:
# WireGuard client monitoring
*/5 * * * * /opt/wireguard-manager/scripts/monitor-clients.sh
# Daily log rotation
0 0 * * * find /opt/wireguard-manager/logs -name "*.log" -mtime +30 -delete
Create management wrapper script
Build a main management script that provides a unified interface for all WireGuard client operations.
#!/bin/bash
# WireGuard Manager - Unified client management interface
set -euo pipefail
SCRIPT_DIR="/opt/wireguard-manager/scripts"
show_help() {
cat << EOF
WireGuard Manager - Client Management Tool
Usage: wg-manager
sudo chmod 755 /opt/wireguard-manager/wg-manager
sudo ln -sf /opt/wireguard-manager/wg-manager /usr/local/bin/wg-manager
Initialize the management system
Set up the initial configuration with your server details and run the first monitoring check.
# Update database with server information
sudo jq --arg endpoint "vpn.example.com:51820" \
--arg dns "1.1.1.1,8.8.8.8" \
'.server_endpoint = $endpoint | .dns_servers = ($dns | split(","))' \
/opt/wireguard-manager/clients/database.json > /tmp/db.json
sudo mv /tmp/db.json /opt/wireguard-manager/clients/database.json
# Run initial monitoring
sudo /opt/wireguard-manager/scripts/monitor-clients.sh
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# WireGuard Client Management Automation Install Script
# Production-grade installation with multi-distro support
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration
WG_MANAGER_DIR="/opt/wireguard-manager"
WG_CONFIG_DIR="/etc/wireguard"
SERVER_ENDPOINT=""
VPN_SUBNET="10.0.0.0/24"
# Progress counter
STEP=0
TOTAL_STEPS=8
# Functions
log() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
success() {
echo -e "${GREEN}✓${NC} $1"
}
error() {
echo -e "${RED}✗${NC} $1" >&2
}
warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
progress() {
STEP=$((STEP + 1))
echo -e "\n${BLUE}[$STEP/$TOTAL_STEPS]${NC} $1"
}
usage() {
echo "Usage: $0 [SERVER_ENDPOINT] [VPN_SUBNET]"
echo "Example: $0 vpn.example.com:51820 10.0.0.0/24"
echo "If not provided, you'll be prompted to configure later"
exit 1
}
cleanup() {
if [[ $? -ne 0 ]]; then
error "Installation failed. Cleaning up..."
rm -rf "$WG_MANAGER_DIR" 2>/dev/null || true
fi
}
trap cleanup ERR EXIT
# Parse arguments
if [[ $# -gt 2 ]]; then
usage
fi
SERVER_ENDPOINT="${1:-}"
VPN_SUBNET="${2:-10.0.0.0/24}"
# Check root privileges
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
exit 1
fi
# Detect distribution
progress "Detecting Linux distribution"
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect Linux distribution"
exit 1
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
EPEL_INSTALL=""
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
EPEL_INSTALL="dnf install -y epel-release"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
EPEL_INSTALL=""
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
EPEL_INSTALL="yum install -y epel-release"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
success "Detected $PRETTY_NAME"
# Verify WireGuard server installation
progress "Verifying WireGuard server installation"
if ! command -v wg &> /dev/null; then
error "WireGuard is not installed. Please install WireGuard server first."
exit 1
fi
if [[ ! -f "$WG_CONFIG_DIR/wg0.conf" ]]; then
warning "WireGuard server configuration not found at $WG_CONFIG_DIR/wg0.conf"
warning "Please ensure WireGuard server is properly configured"
fi
success "WireGuard installation verified"
# Install dependencies
progress "Installing required dependencies"
log "Updating package repositories..."
$PKG_UPDATE
if [[ -n "$EPEL_INSTALL" ]]; then
log "Installing EPEL repository..."
$EPEL_INSTALL
fi
log "Installing packages..."
$PKG_INSTALL qrencode jq curl wireguard-tools
success "Dependencies installed successfully"
# Create directory structure
progress "Creating management directory structure"
mkdir -p "$WG_MANAGER_DIR"/{scripts,clients,logs,templates}
mkdir -p "$WG_MANAGER_DIR/clients"/{active,revoked,configs}
# Set proper permissions
chown -R root:root "$WG_MANAGER_DIR"
chmod -R 755 "$WG_MANAGER_DIR"
chmod 700 "$WG_MANAGER_DIR/clients"
chmod 700 "$WG_MANAGER_DIR/logs"
success "Directory structure created"
# Initialize client database
progress "Creating client database"
cat > "$WG_MANAGER_DIR/clients/database.json" << 'EOF'
{
"clients": [],
"next_ip": 2,
"subnet": "10.0.0.0/24",
"server_public_key": "",
"server_endpoint": "",
"dns_servers": ["1.1.1.1", "8.8.8.8"]
}
EOF
chmod 600 "$WG_MANAGER_DIR/clients/database.json"
success "Client database initialized"
# Create configuration template
progress "Creating client configuration template"
cat > "$WG_MANAGER_DIR/templates/client.conf" << 'EOF'
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = CLIENT_IP/32
DNS = DNS_SERVERS
[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = SERVER_ENDPOINT
PersistentKeepalive = 25
EOF
chmod 644 "$WG_MANAGER_DIR/templates/client.conf"
success "Configuration template created"
# Create provisioning script
progress "Creating client provisioning script"
cat > "$WG_MANAGER_DIR/scripts/provision-client.sh" << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
# WireGuard Client Provisioning Script
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
SERVER_CONFIG="/etc/wireguard/wg0.conf"
TEMPLATE="$WG_DIR/templates/client.conf"
LOG_FILE="$WG_DIR/logs/provisioning.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <client_name> [server_endpoint] [dns_servers]"
echo "Example: $0 john-laptop vpn.example.com:51820 '1.1.1.1,8.8.8.8'"
exit 1
fi
CLIENT_NAME="$1"
SERVER_ENDPOINT="${2:-$(jq -r '.server_endpoint' "$CLIENT_DB")}"
DNS_SERVERS="${3:-$(jq -r '.dns_servers | join(",")' "$CLIENT_DB")}"
# Validate client name
if [[ ! "$CLIENT_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
log "ERROR: Client name must contain only alphanumeric characters, hyphens, and underscores"
exit 1
fi
# Check if client exists
if jq -e ".clients[] | select(.name == \"$CLIENT_NAME\")" "$CLIENT_DB" > /dev/null 2>&1; then
log "ERROR: Client '$CLIENT_NAME' already exists"
exit 1
fi
# Generate keys
CLIENT_PRIVATE_KEY=$(wg genkey)
CLIENT_PUBLIC_KEY=$(echo "$CLIENT_PRIVATE_KEY" | wg pubkey)
# Get next IP
NEXT_IP=$(jq -r '.next_ip' "$CLIENT_DB")
SUBNET_BASE=$(jq -r '.subnet' "$CLIENT_DB" | cut -d'/' -f1 | cut -d'.' -f1-3)
CLIENT_IP="$SUBNET_BASE.$NEXT_IP"
# Get server public key
SERVER_PUBLIC_KEY=$(jq -r '.server_public_key' "$CLIENT_DB")
if [[ "$SERVER_PUBLIC_KEY" == "" || "$SERVER_PUBLIC_KEY" == "null" ]]; then
SERVER_PUBLIC_KEY=$(wg show wg0 public-key 2>/dev/null || echo "")
fi
# Create client config
CLIENT_CONFIG="$WG_DIR/clients/configs/$CLIENT_NAME.conf"
sed -e "s/CLIENT_PRIVATE_KEY/$CLIENT_PRIVATE_KEY/g" \
-e "s/CLIENT_IP/$CLIENT_IP/g" \
-e "s/DNS_SERVERS/$DNS_SERVERS/g" \
-e "s/SERVER_PUBLIC_KEY/$SERVER_PUBLIC_KEY/g" \
-e "s/SERVER_ENDPOINT/$SERVER_ENDPOINT/g" \
"$TEMPLATE" > "$CLIENT_CONFIG"
chmod 600 "$CLIENT_CONFIG"
# Update database
jq --arg name "$CLIENT_NAME" \
--arg ip "$CLIENT_IP" \
--arg pubkey "$CLIENT_PUBLIC_KEY" \
--arg privkey "$CLIENT_PRIVATE_KEY" \
--argjson next_ip "$((NEXT_IP + 1))" \
'.clients += [{
"name": $name,
"ip": $ip,
"public_key": $pubkey,
"private_key": $privkey,
"created": now,
"active": true
}] | .next_ip = $next_ip' "$CLIENT_DB" > "${CLIENT_DB}.tmp"
mv "${CLIENT_DB}.tmp" "$CLIENT_DB"
# Generate QR code
qrencode -t ansiutf8 -r "$CLIENT_CONFIG"
log "Client '$CLIENT_NAME' provisioned successfully"
echo "Configuration saved to: $CLIENT_CONFIG"
EOF
chmod 755 "$WG_MANAGER_DIR/scripts/provision-client.sh"
success "Provisioning script created"
# Create management script
progress "Creating management utilities"
cat > "$WG_MANAGER_DIR/scripts/manage-clients.sh" << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
case "${1:-}" in
list)
echo "Active WireGuard Clients:"
jq -r '.clients[] | select(.active == true) | "\(.name) - \(.ip) - Created: \(.created | strftime("%Y-%m-%d %H:%M"))"' "$CLIENT_DB"
;;
revoke)
if [[ $# -ne 2 ]]; then
echo "Usage: $0 revoke <client_name>"
exit 1
fi
CLIENT_NAME="$2"
jq --arg name "$CLIENT_NAME" '(.clients[] | select(.name == $name) | .active) = false' "$CLIENT_DB" > "${CLIENT_DB}.tmp"
mv "${CLIENT_DB}.tmp" "$CLIENT_DB"
mv "$WG_DIR/clients/configs/$CLIENT_NAME.conf" "$WG_DIR/clients/revoked/" 2>/dev/null || true
echo "Client '$CLIENT_NAME' revoked"
;;
show)
if [[ $# -ne 2 ]]; then
echo "Usage: $0 show <client_name>"
exit 1
fi
CLIENT_NAME="$2"
if [[ -f "$WG_DIR/clients/configs/$CLIENT_NAME.conf" ]]; then
cat "$WG_DIR/clients/configs/$CLIENT_NAME.conf"
echo -e "\nQR Code:"
qrencode -t ansiutf8 -r "$WG_DIR/clients/configs/$CLIENT_NAME.conf"
else
echo "Client configuration not found"
exit 1
fi
;;
*)
echo "Usage: $0 {list|revoke|show} [client_name]"
echo " list - Show all active clients"
echo " revoke <name> - Revoke client access"
echo " show <name> - Show client config and QR code"
;;
esac
EOF
chmod 755 "$WG_MANAGER_DIR/scripts/manage-clients.sh"
# Create symlinks for easy access
ln -sf "$WG_MANAGER_DIR/scripts/provision-client.sh" /usr/local/bin/wg-provision
ln -sf "$WG_MANAGER_DIR/scripts/manage-clients.sh" /usr/local/bin/wg-manage
success "Management utilities created"
# Final verification
progress "Verifying installation"
if [[ -d "$WG_MANAGER_DIR" ]] && [[ -f "$WG_MANAGER_DIR/clients/database.json" ]] && [[ -x "$WG_MANAGER_DIR/scripts/provision-client.sh" ]]; then
success "Installation verification passed"
else
error "Installation verification failed"
exit 1
fi
# Show completion message
echo -e "\n${GREEN}WireGuard Client Management System installed successfully!${NC}"
echo
echo "Next steps:"
echo "1. Configure your server endpoint and subnet in the database:"
echo " $WG_MANAGER_DIR/clients/database.json"
echo
echo "2. Provision a new client:"
echo " wg-provision client-name [server-endpoint] [dns-servers]"
echo
echo "3. Manage existing clients:"
echo " wg-manage list|revoke|show [client-name]"
echo
echo "Configuration directory: $WG_MANAGER_DIR"
echo "Logs directory: $WG_MANAGER_DIR/logs"
Review the script before running. Execute with: bash install.sh