AI for WireGuard Networking — describe your topology, get working configs.
WireGuard is simple by design. The config file format is small. The key generation is one command. But topology is where it gets interesting — mesh networks, hub-and-spoke, split tunneling, multi-plane architectures, NAT traversal. This model knows all of it. Tell it what you want in English, and it generates every config file for every node.
The Modelfile encodes WireGuard internals, config syntax, routing patterns, and the kvpn wrapper.
The context script reads your live tunnel state. The fleet generator builds configs for N nodes automatically.
1. The WireGuard Modelfile
This system prompt covers the full WireGuard surface area: config format, key management, routing, NAT traversal, DNS, split tunneling, multi-plane design, and troubleshooting.
Complete WireGuard expert Modelfile
# /srv/ollama/Modelfile.wireguard-expert
FROM llama3.1:8b
SYSTEM """
You are a WireGuard networking expert for this kldload-based infrastructure.
You generate complete, working config files. You design topologies.
You debug connectivity issues by reading 'wg show' output and routing tables.
=== CONFIG FILE FORMAT ===
/etc/wireguard/wg0.conf:
[Interface]
PrivateKey = BASE64_PRIVATE_KEY
Address = 10.100.0.1/24 # This node's tunnel IP
ListenPort = 51820 # UDP port (optional on clients)
DNS = 10.100.0.1 # Optional DNS server
MTU = 1420 # Default. Lower for PPPoE (1392) or nested tunnels
Table = auto # Routing table (auto, off, or number)
PreUp = COMMAND # Run before interface up
PostUp = COMMAND # Run after interface up (e.g., iptables for NAT)
PreDown = COMMAND # Run before interface down
PostDown = COMMAND # Run after interface down
[Peer]
PublicKey = BASE64_PUBLIC_KEY
PresharedKey = BASE64_PSK # Optional. Adds post-quantum-resistant layer.
AllowedIPs = 10.100.0.2/32 # IPs this peer can send FROM (also acts as routing)
Endpoint = 203.0.113.5:51820 # Peer's public IP:port (optional for server-side)
PersistentKeepalive = 25 # Seconds. Required if peer is behind NAT.
=== KEY MANAGEMENT ===
Generate private key: wg genkey > /etc/wireguard/private.key
Derive public key: cat /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
Generate PSK: wg genpsk > /etc/wireguard/psk.key
One-liner: wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
Permissions: chmod 600 /etc/wireguard/private.key /etc/wireguard/psk.key
kvpn key generation: kvpn genkeys (generates and stores keys automatically)
=== TOPOLOGY PATTERNS ===
Hub-and-spoke (star):
- Hub has AllowedIPs covering all spokes: AllowedIPs = 10.100.0.0/24
- Spokes have AllowedIPs = 10.100.0.1/32 (hub only) or 0.0.0.0/0 (route all traffic)
- Hub runs: PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; sysctl -w net.ipv4.ip_forward=1
- Spokes set Endpoint to hub's public IP
- Spokes behind NAT set PersistentKeepalive = 25
Full mesh:
- Every node has every other node as a [Peer]
- N nodes = N*(N-1)/2 unique peer relationships
- AllowedIPs = PEER_TUNNEL_IP/32 for each peer
- All nodes with public IPs set Endpoint
- NAT'd nodes set PersistentKeepalive = 25
- Scale limit: ~20-30 nodes before config management becomes painful
Hub-and-spoke with site-to-site:
- Hub routes between spokes: AllowedIPs includes spoke LAN subnets
- Spoke A: AllowedIPs = 10.100.0.0/24, 192.168.1.0/24 (spoke B's LAN)
- Hub forwards between WireGuard and spoke LANs
Multi-plane architecture:
- Plane 1 (management): wg0, 10.100.0.0/24 — SSH, monitoring, configuration
- Plane 2 (data): wg1, 10.200.0.0/24 — ZFS replication, NFS, iSCSI
- Plane 3 (application): wg2, 10.300.0.0/24 — Service mesh, API traffic
- Plane 4 (backup): wg3, 10.400.0.0/24 — Dedicated syncoid replication path
- Each plane has its own keys, its own ports, its own firewall rules
- Failure in one plane does not affect others
=== SPLIT TUNNELING ===
Route only specific subnets:
AllowedIPs = 10.0.0.0/8, 172.16.0.0/12 (private subnets through tunnel)
# Everything else goes through default gateway (internet bypasses tunnel)
Route all traffic (full tunnel):
AllowedIPs = 0.0.0.0/0, ::/0
# Requires DNS setting and hub NAT masquerade
# Hub: PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Exclude specific subnets:
# WireGuard doesn't support exclude rules directly
# Use finer AllowedIPs to route around exclusions
# Example: route 10.0.0.0/8 EXCEPT 10.5.0.0/16
AllowedIPs = 10.0.0.0/9, 10.128.0.0/9, 10.4.0.0/14, 10.6.0.0/15, 10.8.0.0/13, ...
=== NAT TRAVERSAL ===
Both peers behind NAT:
- One peer MUST have a public IP or port forward
- PersistentKeepalive = 25 on the NAT'd peer keeps the UDP session open
- If both are behind CGNAT: use a relay node with a public IP
STUN-like behavior:
- WireGuard doesn't need STUN. The Endpoint tells the peer where to send.
- If a peer's public IP changes, WireGuard updates automatically on next handshake.
- Roaming: WireGuard detects endpoint changes within 5 minutes (handshake interval).
Port forwarding:
- Forward UDP port 51820 on router to WireGuard host
- Or use a non-standard port: ListenPort = 443 (passes through more firewalls)
=== DNS ===
Set DNS for tunnel: DNS = 10.100.0.1 (in [Interface])
Requires: systemd-resolved or resolvconf installed
Manual /etc/resolv.conf: PostUp = echo 'nameserver 10.100.0.1' > /etc/resolv.conf
PostDown = cp /etc/resolv.conf.bak /etc/resolv.conf
DNS leak prevention: Route DNS traffic through tunnel: AllowedIPs includes DNS_IP/32
=== FIREWALL INTEGRATION ===
nftables (kfw):
kfw open 51820/udp # Allow WireGuard traffic
kfw open 22/tcp --interface wg0 # Allow SSH only over tunnel
PostUp = nft add rule inet filter input iif wg0 accept
PostDown = nft delete rule inet filter input handle N
iptables:
PostUp = iptables -A INPUT -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D INPUT -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
=== KLDLOAD WIREGUARD TOOLS ===
kvpn WireGuard tunnel manager
kvpn genkeys Generate key pair
kvpn add-peer Add peer interactively: kvpn add-peer --name node2 --endpoint IP:PORT
kvpn show Show all tunnels and peers (wraps 'wg show')
kvpn status Connection status with latency and transfer stats
kfw nftables firewall manager
kfw open 51820/udp Open WireGuard port
=== TROUBLESHOOTING ===
Peer not connecting:
1. wg show (check 'latest handshake' — if blank, no handshake has succeeded)
2. Check firewall: is UDP 51820 open on BOTH sides?
3. Check endpoint: is the Endpoint IP reachable? ping ENDPOINT_IP
4. Check keys: does each side have the OTHER's public key? (most common mistake)
5. Check AllowedIPs: do they match what the peer is sending?
6. Check NAT: if behind NAT, is PersistentKeepalive set?
7. Check MTU: if large packets fail, lower MTU to 1392 or 1280
Handshake succeeds but no traffic:
1. Check AllowedIPs routing — packets must match AllowedIPs to be accepted
2. Check ip route — is the tunnel route installed?
3. Check ip forwarding: sysctl net.ipv4.ip_forward (must be 1 for routing)
4. Check firewall on both sides: is forwarding allowed?
5. Test with ping to tunnel IP first (10.100.0.X), then remote LAN
Performance issues:
1. MTU: lower if seeing fragmentation (tcpdump -i wg0 shows fragments)
2. CPU: WireGuard is fast but still CPU-bound. Check 'perf top' for crypto overhead
3. Single-threaded: WireGuard uses one CPU core per interface. Add more interfaces for more throughput
4. iperf3 through tunnel vs direct to measure overhead
=== PHILOSOPHY ===
WireGuard is simple because it does one thing: encrypted point-to-point tunnels.
Topology, routing, DNS, firewalling — those are YOUR design decisions.
The config file is small on purpose. The complexity lives in the network design, not the tool.
Every peer is equal. There is no 'server' and 'client' — only peers with different AllowedIPs.
"""
PARAMETER temperature 0.3
PARAMETER num_ctx 16384
# Build the WireGuard expert model
ollama create wireguard-expert -f /srv/ollama/Modelfile.wireguard-expert
# Verify it
ollama run wireguard-expert "Design a 4-node mesh with one hub behind a static IP and 3 spokes behind NAT"
2. Live context script
The context script feeds your live WireGuard state, interface configuration, routing table, and firewall rules into every query. The AI sees your actual peers, handshake times, and transfer stats.
The WireGuard context builder
#!/bin/bash
# /usr/local/bin/kai-wg — query the WireGuard AI with live tunnel context
build_wg_context() {
echo "=== LIVE WIREGUARD STATE ($(date -Iseconds)) ==="
echo -e "\n--- wg show (all interfaces) ---"
wg show 2>/dev/null || echo "(no WireGuard interfaces active)"
echo -e "\n--- WireGuard interfaces ---"
ip -br link show type wireguard 2>/dev/null
echo -e "\n--- WireGuard interface addresses ---"
for iface in $(ip -br link show type wireguard 2>/dev/null | awk '{print $1}'); do
echo " $iface:"
ip addr show dev "$iface" 2>/dev/null | grep inet
done
echo -e "\n--- Config files ---"
for conf in /etc/wireguard/*.conf; do
[ -f "$conf" ] || continue
echo " === $(basename "$conf") ==="
# Show config but redact private keys
sed 's/PrivateKey = .*/PrivateKey = [REDACTED]/' "$conf" 2>/dev/null
done
echo -e "\n--- Routing table ---"
ip route 2>/dev/null
echo -e "\n--- Routing table (WireGuard-related) ---"
ip route 2>/dev/null | grep -E 'wg[0-9]|10\.(100|200|300|400)\.' || echo "(no WireGuard routes)"
echo -e "\n--- All network interfaces ---"
ip -br addr 2>/dev/null
echo -e "\n--- Firewall rules (WireGuard ports) ---"
nft list ruleset 2>/dev/null | grep -E '51820|wg[0-9]|wireguard' | head -20 || \
iptables -L -n 2>/dev/null | grep -E '51820|wg' | head -10 || \
echo "(no WireGuard firewall rules found)"
echo -e "\n--- Listening UDP ports ---"
ss -ulnp 2>/dev/null | grep -E '51820|wg' || ss -ulnp 2>/dev/null | head -10
echo -e "\n--- IP forwarding ---"
sysctl net.ipv4.ip_forward 2>/dev/null
sysctl net.ipv6.conf.all.forwarding 2>/dev/null
echo -e "\n--- Hostname and public IP ---"
echo "Hostname: $(hostname)"
curl -s --max-time 3 ifconfig.me 2>/dev/null || echo "(public IP lookup failed — offline or blocked)"
}
QUESTION="$*"
if [ -z "$QUESTION" ]; then
echo "Usage: kai-wg <question>"
echo ""
echo "Examples:"
echo " kai-wg 'add a new peer to my mesh'"
echo " kai-wg 'set up split tunneling for 10.0.0.0/8'"
echo " kai-wg 'debug why peer node-3 is not connecting'"
echo " kai-wg 'design a 4-plane WireGuard mesh for 8 nodes'"
echo " kai-wg 'generate configs for a hub-and-spoke with 5 spokes'"
echo " kai-wg 'why is my tunnel throughput low?'"
exit 1
fi
CONTEXT=$(build_wg_context)
echo -e "${CONTEXT}\n\n=== QUESTION ===\n${QUESTION}" | ollama run wireguard-expert
3. Example queries
The model reads your live tunnel state and generates complete, working configurations. Ask it to design a topology and it gives you every config file for every node.
"Add a new peer to my mesh"
The AI reads your existing wg show output, identifies the next available tunnel IP
in the subnet, generates a key pair, writes the [Peer] block for your existing config,
and generates the complete wg0.conf for the new node. It reminds you to run
kvpn add-peer or wg-quick down wg0 && wg-quick up wg0.
kai-wg "add a new peer called node-6 at 203.0.113.50 to my mesh"
"Set up split tunneling for 10.0.0.0/8"
The AI modifies the AllowedIPs on your existing peer config to route only
the 10.0.0.0/8 subnet through the tunnel while keeping internet traffic on the default gateway.
It shows the exact config diff and explains how WireGuard's AllowedIPs doubles as both
an ACL and a routing table.
kai-wg "set up split tunneling — route 10.0.0.0/8 through the tunnel, everything else direct"
"Debug why peer isn't connecting"
The AI reads wg show and checks: Is there a latest handshake? (If not, no handshake succeeded.)
It checks the endpoint is reachable, the firewall allows UDP 51820, AllowedIPs match, and
PersistentKeepalive is set if behind NAT. Gives you a step-by-step diagnostic.
kai-wg "peer node-3 shows no latest handshake — why can't it connect?"
"Design a 4-plane WireGuard mesh"
The AI generates a complete multi-plane architecture: management (wg0), data (wg1), application (wg2), backup (wg3). Each plane gets its own subnet, its own port, its own keys. It generates every config file for every node and explains the isolation benefits.
kai-wg "design a 4-plane WireGuard mesh for 6 nodes — management, data, app, backup"
"Generate configs for N nodes"
Tell the AI how many nodes, what topology, and which subnets. It generates
complete config files for every node — keys, endpoints, AllowedIPs, PostUp/PostDown rules.
Copy each file to its node and run wg-quick up wg0.
kai-wg "generate a full mesh config for 8 nodes, hub at 203.0.113.1, spokes behind NAT"
"Migrate from OpenVPN to WireGuard"
The AI reads your existing network interfaces and routing table, maps your current VPN topology to WireGuard equivalents, generates the new configs, and gives you a migration plan that avoids downtime: bring WireGuard up alongside OpenVPN, test, then cut over.
kai-wg "I have an OpenVPN hub-and-spoke — migrate to WireGuard with zero downtime"
4. Fleet config generation and monitoring via cron
Generate configs for your entire fleet from a single description. Monitor tunnel health continuously. The AI detects stale handshakes, peers that disappeared, and routing asymmetries.
Fleet config generator
#!/bin/bash
# /usr/local/bin/kai-wg-fleet — generate WireGuard configs for N nodes
FLEET_DIR="/srv/wireguard-fleet"
mkdir -p "$FLEET_DIR"
# Define your fleet
NODES=(
"node-1:203.0.113.1:10.100.0.1" # name:public_ip:tunnel_ip
"node-2:203.0.113.2:10.100.0.2"
"node-3:NAT:10.100.0.3" # NAT = no public IP
"node-4:NAT:10.100.0.4"
"node-5:203.0.113.5:10.100.0.5"
)
SUBNET="10.100.0.0/24"
PORT=51820
# Generate keys for each node
for entry in "${NODES[@]}"; do
name=$(echo "$entry" | cut -d: -f1)
keydir="$FLEET_DIR/$name"
mkdir -p "$keydir"
if [ ! -f "$keydir/private.key" ]; then
wg genkey | tee "$keydir/private.key" | wg pubkey > "$keydir/public.key"
wg genpsk > "$keydir/psk.key"
chmod 600 "$keydir/private.key" "$keydir/psk.key"
echo "Generated keys for $name"
fi
done
# Generate configs (full mesh)
for entry in "${NODES[@]}"; do
name=$(echo "$entry" | cut -d: -f1)
pub_ip=$(echo "$entry" | cut -d: -f2)
tun_ip=$(echo "$entry" | cut -d: -f3)
conf="$FLEET_DIR/$name/wg0.conf"
priv=$(cat "$FLEET_DIR/$name/private.key")
cat > "$conf" <<WGCONF
[Interface]
PrivateKey = $priv
Address = ${tun_ip}/24
ListenPort = $PORT
WGCONF
# Add every other node as a peer
for peer_entry in "${NODES[@]}"; do
peer_name=$(echo "$peer_entry" | cut -d: -f1)
[ "$peer_name" = "$name" ] && continue
peer_pub_ip=$(echo "$peer_entry" | cut -d: -f2)
peer_tun_ip=$(echo "$peer_entry" | cut -d: -f3)
peer_pubkey=$(cat "$FLEET_DIR/$peer_name/public.key")
peer_psk=$(cat "$FLEET_DIR/$peer_name/psk.key")
echo "" >> "$conf"
echo "[Peer]" >> "$conf"
echo "PublicKey = $peer_pubkey" >> "$conf"
echo "PresharedKey = $peer_psk" >> "$conf"
echo "AllowedIPs = ${peer_tun_ip}/32" >> "$conf"
[ "$peer_pub_ip" != "NAT" ] && echo "Endpoint = ${peer_pub_ip}:${PORT}" >> "$conf"
echo "PersistentKeepalive = 25" >> "$conf"
done
echo "Generated: $conf"
done
echo ""
echo "Fleet configs in: $FLEET_DIR"
echo "Deploy: scp \$FLEET_DIR/node-N/wg0.conf root@node-N:/etc/wireguard/wg0.conf"
Tunnel health monitor
#!/bin/bash
# /usr/local/bin/kai-wg-monitor — AI-driven WireGuard health check
REPORT_DIR="/var/log/kai-wireguard"
mkdir -p "$REPORT_DIR"
REPORT="$REPORT_DIR/$(date +%F).txt"
STATE=$(cat <<WGDATA
=== WIREGUARD HEALTH CHECK — $(hostname) — $(date) ===
--- wg show ---
$(wg show 2>/dev/null || echo "(no tunnels)")
--- Interface addresses ---
$(for iface in $(ip -br link show type wireguard 2>/dev/null | awk '{print $1}'); do
echo "$iface: $(ip -br addr show dev "$iface" 2>/dev/null | awk '{print $3}')"
done)
--- Routing table ---
$(ip route 2>/dev/null)
--- Firewall (WireGuard) ---
$(nft list ruleset 2>/dev/null | grep -E '51820|wg' | head -10)
--- Peer handshake ages ---
$(wg show all latest-handshakes 2>/dev/null | while read iface pubkey ts; do
if [ "$ts" -gt 0 ] 2>/dev/null; then
age=$(( $(date +%s) - ts ))
echo "$iface peer=${pubkey:0:8}... last_handshake=${age}s ago"
else
echo "$iface peer=${pubkey:0:8}... NEVER CONNECTED"
fi
done)
--- IP forwarding ---
$(sysctl net.ipv4.ip_forward 2>/dev/null)
--- UDP listening ---
$(ss -ulnp 2>/dev/null | grep -E '51820|wg')
WGDATA
)
ANALYSIS=$(echo "${STATE}
Analyze this WireGuard health data. Report:
1. CONNECTIVITY — which peers have recent handshakes, which are stale (>5 min), which never connected
2. ROUTING — are routes installed correctly for all tunnel subnets
3. FIREWALL — is UDP 51820 open, are tunnel interfaces allowed through
4. SECURITY — any unexpected peers, missing PresharedKeys, exposed private keys
5. RECOMMENDATIONS — exact kvpn or wg commands to fix any issues
Be specific. Reference actual peer public keys and interface names." | \
ollama run wireguard-expert)
{
echo "=== AI WIREGUARD HEALTH REPORT ==="
echo "=== $(hostname) — $(date) ==="
echo ""
echo "$ANALYSIS"
echo ""
echo "=== RAW DATA ==="
echo "$STATE"
} > "$REPORT"
echo "WireGuard report saved: $REPORT"
Schedule it
# Hourly WireGuard health check
cat > /etc/cron.d/kai-wg-monitor <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
0 * * * * root /usr/local/bin/kai-wg-monitor
EOF
# Quick connectivity check every 5 minutes (no AI, just handshake age)
cat > /etc/cron.d/wg-handshake-check <<'CRON'
SHELL=/bin/bash
*/5 * * * * root wg show all latest-handshakes 2>/dev/null | while read iface pubkey ts; do \
[ "$ts" -gt 0 ] 2>/dev/null || continue; \
age=$(( $(date +%s) - ts )); \
[ "$age" -gt 300 ] && logger -t wg-alert -p daemon.warning "STALE: $iface peer ${pubkey:0:8}... handshake ${age}s ago"; \
done
CRON
# Check reports
cat /var/log/kai-wireguard/$(date +%F).txt
5. Replicate to fleet via syncoid
The WireGuard expert model and monitoring tools deploy to every node through ZFS replication. Each node monitors its own tunnels with the same analytical model.
Fleet deployment
#!/bin/bash
# replicate-wg-expert.sh — push the WireGuard model to all nodes
NODES="node-2 node-3 node-4 node-5"
# Snapshot the trained model
zfs snapshot rpool/srv/ollama@wireguard-expert-$(date +%F)
# Replicate to every node
for node in $NODES; do
echo "--- Syncing WireGuard expert to $node ---"
syncoid --no-sync-snap rpool/srv/ollama "root@${node}:rpool/srv/ollama"
ssh "root@${node}" "systemctl restart ollama"
echo "$node: done"
done
# Deploy scripts and cron jobs
for node in $NODES; do
scp /usr/local/bin/kai-wg "root@${node}:/usr/local/bin/kai-wg"
scp /usr/local/bin/kai-wg-monitor "root@${node}:/usr/local/bin/kai-wg-monitor"
scp /usr/local/bin/kai-wg-fleet "root@${node}:/usr/local/bin/kai-wg-fleet"
scp /etc/cron.d/kai-wg-monitor "root@${node}:/etc/cron.d/kai-wg-monitor"
scp /etc/cron.d/wg-handshake-check "root@${node}:/etc/cron.d/wg-handshake-check"
ssh "root@${node}" "chmod +x /usr/local/bin/kai-wg /usr/local/bin/kai-wg-monitor /usr/local/bin/kai-wg-fleet"
done
echo "Fleet updated at $(date)"
WireGuard is not complicated. Networking is. The config file fits on a napkin. The topology design fits on a whiteboard. But getting the AllowedIPs right, the routing right, the NAT traversal right, the firewall right — that's where the expertise lives. This model carries that expertise. Describe what you want. Get configs that work.
Every peer is equal. Every tunnel is encrypted. Every config is three commands from working. Learn the primitives. Then let the AI handle the combinatorics.