| your Linux construction kit
Source
← Back to AI Admin Assistant

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"
A network engineer carries the RFC in their head. This model carries the RFC, your topology, your peer list, and your routing table. It designs meshes while you describe them in English.

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
You debug a VPN by reading 'wg show' and the routing table. This script makes sure the AI reads both before it says a word about your tunnels.

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"
Writing 5 WireGuard configs by hand means 5 chances to paste the wrong public key. This script generates all of them from one definition with zero copy-paste errors.

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
A stale handshake means a dead tunnel. You might not notice until someone complains they can't reach node-3. This cron job notices in 5 minutes.

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)"
Train the WireGuard expert once. syncoid sends only the changed blocks. Every node gets the same networking brain, monitors its own tunnels, and reports its own health. One teacher. Many students. Same curriculum. Different homework.

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.