VPN Server Setup

Configure WireGuard VPN servers for secure network connectivity between Zero clients and your infrastructure. This guide covers single-server and multi-region deployments.

Architecture: Zero uses WireGuard for its VPN layer due to its modern cryptography, minimal attack surface, and excellent performance.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     Client Devices                          │
│   (Linux, Windows, macOS, Android, iOS)                     │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ WireGuard (UDP 51820)
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   VPN Gateway Cluster                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  US-East    │  │  EU-West    │  │  APAC       │         │
│  │  Gateway    │  │  Gateway    │  │  Gateway    │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   Zero Backend                          │
│              (API, Database, File Sync)                     │
└─────────────────────────────────────────────────────────────┘

Single Server Setup

Install WireGuard

# Ubuntu/Debian
sudo apt update
sudo apt install wireguard wireguard-tools

# RHEL/Rocky/Alma
sudo dnf install wireguard-tools

# Verify installation
wg --version

Generate Server Keys

# Generate server keypair
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key

# Secure the private key
chmod 600 /etc/wireguard/server_private.key

# View keys
cat /etc/wireguard/server_private.key
cat /etc/wireguard/server_public.key

Server Configuration

# /etc/wireguard/wg0.conf
[Interface]
Address = 10.100.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY_HERE

# Enable IP forwarding
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# Peer configuration is managed dynamically by Zero API
# Clients are added via: wg set wg0 peer PUBLIC_KEY allowed-ips IP/32

Enable IP Forwarding

# Permanent IP forwarding
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Start VPN Service

# Start WireGuard
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

# Check status
sudo wg show

API Integration

Zero manages VPN peers dynamically through its API:

VPN Management Service

# /opt/zero/vpn-agent/config.yaml
api_url: http://localhost:8080
api_token: your-vpn-service-token
wireguard_interface: wg0
subnet: 10.100.0.0/24
sync_interval: 30s

# IP allocation range
ip_pool:
  start: 10.100.0.10
  end: 10.100.0.254

Dynamic Peer Management

The VPN agent syncs with the Zero API to manage peers:

#!/bin/bash
# Example: Add peer from API response

# Fetch active device configurations
PEERS=$(curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.zero.io/api/v1/vpn/peers)

# Apply each peer
echo "$PEERS" | jq -c '.[]' | while read peer; do
  PUBLIC_KEY=$(echo $peer | jq -r '.public_key')
  ALLOWED_IP=$(echo $peer | jq -r '.allowed_ip')
  
  wg set wg0 peer $PUBLIC_KEY allowed-ips $ALLOWED_IP/32
done

Multi-Region Deployment

Regional Configuration

Region Subnet Endpoint
US-East 10.100.0.0/24 us-vpn.zero.io:51820
EU-West 10.101.0.0/24 eu-vpn.zero.io:51820
APAC 10.102.0.0/24 apac-vpn.zero.io:51820

Geographic Routing

Use DNS-based routing to direct clients to the nearest VPN server:

# Cloudflare Load Balancer example
# vpn.zero.io resolves based on client location

# US clients → us-vpn.zero.io
# EU clients → eu-vpn.zero.io  
# APAC clients → apac-vpn.zero.io

Inter-Region Connectivity

# Site-to-site tunnel between regions
# US-East server (/etc/wireguard/wg1.conf)

[Interface]
Address = 10.200.0.1/24
ListenPort = 51821
PrivateKey = US_PRIVATE_KEY

[Peer]
# EU-West server
PublicKey = EU_PUBLIC_KEY
Endpoint = eu-vpn.zero.io:51821
AllowedIPs = 10.101.0.0/24, 10.200.0.2/32
PersistentKeepalive = 25

[Peer]
# APAC server
PublicKey = APAC_PUBLIC_KEY
Endpoint = apac-vpn.zero.io:51821
AllowedIPs = 10.102.0.0/24, 10.200.0.3/32
PersistentKeepalive = 25

High Availability

Active-Passive Setup

# Using keepalived for VIP failover

# /etc/keepalived/keepalived.conf
vrrp_instance VPN_VIP {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    
    authentication {
        auth_type PASS
        auth_pass secretpassword
    }
    
    virtual_ipaddress {
        203.0.113.100/32  # Floating VIP
    }
    
    track_script {
        check_wg
    }
}

vrrp_script check_wg {
    script "/usr/local/bin/check-wireguard.sh"
    interval 2
    weight -20
}

Load Balancing (Active-Active)

# Using ECMP or cloud load balancer

# AWS Network Load Balancer
resource "aws_lb" "vpn" {
  name               = "zero-vpn"
  internal           = false
  load_balancer_type = "network"
  
  subnet_mapping {
    subnet_id = aws_subnet.public.id
  }
}

resource "aws_lb_target_group" "vpn" {
  name     = "zero-vpn"
  port     = 51820
  protocol = "UDP"
  vpc_id   = aws_vpc.main.id
  
  health_check {
    protocol = "TCP"
    port     = 8080  # Health check endpoint
  }
}

Security Hardening

Firewall Rules

# Allow only WireGuard and health checks
sudo ufw default deny incoming
sudo ufw allow 51820/udp comment "WireGuard"
sudo ufw allow 8080/tcp from 10.0.0.0/8 comment "Health checks"
sudo ufw enable

Connection Rate Limiting

# Limit new connections with iptables
iptables -A INPUT -p udp --dport 51820 -m state --state NEW \
  -m recent --set --name wg_ratelimit

iptables -A INPUT -p udp --dport 51820 -m state --state NEW \
  -m recent --update --seconds 60 --hitcount 10 --name wg_ratelimit \
  -j DROP

Automatic Key Rotation

Zero rotates client keys automatically. Server key rotation:

#!/bin/bash
# Rotate server keys quarterly

# Generate new keypair
NEW_PRIVATE=$(wg genkey)
NEW_PUBLIC=$(echo $NEW_PRIVATE | wg pubkey)

# Update configuration
sed -i "s/PrivateKey = .*/PrivateKey = $NEW_PRIVATE/" /etc/wireguard/wg0.conf

# Notify API of new public key
curl -X POST https://api.zero.io/api/v1/vpn/rotate-key \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"region\": \"us-east\", \"public_key\": \"$NEW_PUBLIC\"}"

# Restart WireGuard
systemctl restart wg-quick@wg0

Monitoring

Prometheus Metrics

# Using prometheus-wireguard-exporter
docker run -d \
  --name wg-exporter \
  --network host \
  --cap-add NET_ADMIN \
  -v /etc/wireguard:/etc/wireguard:ro \
  mindflavor/prometheus-wireguard-exporter

Available Metrics

  • wireguard_peer_rx_bytes - Bytes received per peer
  • wireguard_peer_tx_bytes - Bytes transmitted per peer
  • wireguard_peer_latest_handshake - Last handshake timestamp
  • wireguard_peers_count - Number of active peers

Alerting Rules

groups:
- name: wireguard
  rules:
  - alert: WireGuardPeerDisconnected
    expr: time() - wireguard_peer_latest_handshake > 300
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "WireGuard peer {{ $labels.peer }} disconnected"
      
  - alert: WireGuardHighBandwidth
    expr: rate(wireguard_peer_tx_bytes[5m]) > 100000000  # 100 MB/s
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "High bandwidth usage on WireGuard peer"

Troubleshooting

Issue Diagnosis Solution
Client can't connect sudo wg show Check firewall, verify public key
Handshake fails Check endpoint reachability Verify UDP 51820 is open
No traffic after connect ip route Check IP forwarding, AllowedIPs
High latency mtr vpn.zero.io Use closer regional server

Debug Commands

# Show WireGuard status
sudo wg show

# Check interface
ip addr show wg0

# View routing table
ip route | grep wg0

# Test connectivity
ping 10.100.0.1

# Packet capture
sudo tcpdump -i wg0 -n

Next Steps