Yggdrasil is an end-to-end encrypted, self-healing IPv6 overlay mesh network. Unlike traditional VPNs with a central server, Yggdrasil uses a distributed tree routing algorithm where every node is equal — there's no single point of failure, no central coordinator, and no predefined topology. Nodes discover each other through public or private peers and form an adaptive mesh that routes around failures automatically.
This architecture makes Yggdrasil compelling for distributed systems, edge deployments, and privacy-conscious infrastructure. But it also means monitoring looks fundamentally different: there's no server to ping, no control plane to check, and "the network is up" depends on the local node's view of the mesh. This tutorial shows you how to build meaningful observability for Yggdrasil deployments with Vigilmon.
Why Yggdrasil needs dedicated monitoring
Yggdrasil's decentralized design creates unique observability challenges:
- Node isolation — a node that loses all its configured peers becomes silently isolated. Traffic to and from that node fails, but the node itself stays "running" with a healthy process and interface.
- Peer connection churn — Yggdrasil maintains persistent TCP connections to peers. Network instability, ISP routing changes, or peer host reboots can drop all connections simultaneously without immediate self-healing.
- IPv6 address stability — Yggdrasil derives each node's IPv6 address from its public key. As long as the key is stable, the address is permanent. But a key regeneration (intentional or from data loss) silently changes the node's address, breaking all references to it.
- No built-in health API — Yggdrasil provides an admin socket (
/var/run/yggdrasil.sock) and optional HTTP API, but neither sends alerts. You have to poll. - Services over Yggdrasil — applications running on Yggdrasil IPv6 addresses can fail independently of network health. A service crash is invisible to the mesh layer.
Complete monitoring requires checking node peer connectivity, the Yggdrasil daemon health, and application services running over the overlay.
What you'll need
- A running Yggdrasil node (version 0.4+)
- At least one configured peer
- Services accessible over the Yggdrasil IPv6 address
- A free Vigilmon account
Step 1: Expose health endpoints on Yggdrasil-hosted services
Services bound to Yggdrasil addresses use the node's 200::/7 IPv6 address range. Bind health endpoints to this address:
Go:
package main
import (
"encoding/json"
"net/http"
)
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
"network": "yggdrasil",
})
})
// Bind to Yggdrasil IPv6 address (replace with your node's actual address)
http.ListenAndServe("[200:1234:abcd::1]:8080", nil)
}
Python / Flask:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify(status='ok', network='yggdrasil')
if __name__ == '__main__':
# Replace with your node's Yggdrasil address
app.run(host='200:1234:abcd::1', port=8080)
Nginx (IPv6 listener):
server {
listen [200:1234:abcd::1]:80;
location /health {
access_log off;
return 200 '{"status":"ok"}';
add_header Content-Type application/json;
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Step 2: Monitor the Yggdrasil daemon and peer connectivity
Use the yggdrasilctl command-line tool (or the admin socket API) to check the node's current peer count. A node with zero peers is isolated:
#!/bin/bash
# yggdrasil-peer-probe.sh
# Monitors peer connectivity and pings Vigilmon
HEARTBEAT_URL="https://vigilmon.online/api/heartbeat/YOUR_PEER_HEARTBEAT"
MIN_PEERS=1
LOG_FILE="/var/log/yggdrasil-probe.log"
# Check the Yggdrasil daemon is running
if ! pgrep -x yggdrasil > /dev/null; then
echo "$(date): Yggdrasil daemon not running" >> "$LOG_FILE"
exit 1
fi
# Get peer count from the admin socket
peer_count=$(yggdrasilctl getPeers 2>/dev/null | python3 -c "
import json, sys
try:
d = json.load(sys.stdin)
peers = d.get('peers', {})
connected = sum(1 for p in peers.values() if p.get('up', False))
print(connected)
except:
print(0)
" 2>/dev/null)
if [ -z "$peer_count" ] || [ "$peer_count" -lt "$MIN_PEERS" ]; then
echo "$(date): Yggdrasil peer count is ${peer_count:-0} (minimum: $MIN_PEERS)" >> "$LOG_FILE"
exit 1
fi
echo "$(date): $peer_count peers connected" >> "$LOG_FILE"
curl -s "$HEARTBEAT_URL" --max-time 10 --retry 2
Schedule every 2 minutes:
*/2 * * * * /opt/scripts/yggdrasil-peer-probe.sh
In Vigilmon: Monitors → New Monitor → Heartbeat, interval 5 minutes, name Yggdrasil Peer Connectivity. A node that loses all peers stops pinging and Vigilmon alerts.
Step 3: Monitor the Yggdrasil self-address reachability
Each Yggdrasil node should be able to ping its own overlay address (self-reachability confirms the interface is up and the crypto layer is functional):
#!/bin/bash
# yggdrasil-self-check.sh
HEARTBEAT_URL="https://vigilmon.online/api/heartbeat/YOUR_SELF_CHECK_HEARTBEAT"
# Get self address from yggdrasilctl
self_addr=$(yggdrasilctl getSelf 2>/dev/null | python3 -c "
import json, sys
try:
d = json.load(sys.stdin)
print(d.get('self', {}).get('address', ''))
except:
print('')
" 2>/dev/null)
if [ -z "$self_addr" ]; then
echo "$(date): Could not retrieve Yggdrasil self address" >> /var/log/yggdrasil-self.log
exit 1
fi
# Ping self address to confirm interface + routing stack is healthy
if ! ping6 -c 2 -W 3 "$self_addr" > /dev/null 2>&1; then
echo "$(date): Cannot ping self address $self_addr" >> /var/log/yggdrasil-self.log
exit 1
fi
curl -s "$HEARTBEAT_URL" --max-time 10
Step 4: Probe services over Yggdrasil from another node
Since Vigilmon's global probes run on the public internet, they can't reach Yggdrasil addresses directly. Run a probe from another Yggdrasil node to verify end-to-end service reachability:
#!/bin/bash
# yggdrasil-service-probe.sh
# Run on a Yggdrasil node that should reach critical services
HEARTBEAT_URL="https://vigilmon.online/api/heartbeat/YOUR_SERVICE_HEARTBEAT"
# Services to check — use Yggdrasil IPv6 addresses
SERVICES=(
"http://[200:1234:abcd::10]:8080/health"
"http://[200:1234:abcd::20]:3000/health"
"http://[200:1234:abcd::30]:5000/health"
)
all_ok=true
for svc in "${SERVICES[@]}"; do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 8 -6 "$svc")
if [ "$code" != "200" ]; then
echo "$(date): Service $svc returned $code" >> /var/log/yggdrasil-services.log
all_ok=false
fi
done
$all_ok && curl -s "$HEARTBEAT_URL" --max-time 10
Pair this with a Vigilmon Heartbeat monitor at 5-minute interval, name Yggdrasil Service Health.
Step 5: Monitor the Yggdrasil configuration and key integrity
Key regeneration silently changes a node's IPv6 address, breaking all existing references. Monitor for accidental key changes:
#!/bin/bash
# yggdrasil-key-check.sh
# Verifies the node's public key hasn't changed
HEARTBEAT_URL="https://vigilmon.online/api/heartbeat/YOUR_KEY_HEARTBEAT"
KNOWN_KEY_FILE="/etc/yggdrasil/known_pubkey.txt"
# Get current public key
current_key=$(yggdrasilctl getSelf 2>/dev/null | python3 -c "
import json, sys
try:
d = json.load(sys.stdin)
print(d.get('self', {}).get('key', ''))
except:
print('')
" 2>/dev/null)
if [ -z "$current_key" ]; then
echo "$(date): Could not retrieve public key" >> /var/log/yggdrasil-key.log
exit 1
fi
# On first run, save the key as the baseline
if [ ! -f "$KNOWN_KEY_FILE" ]; then
echo "$current_key" > "$KNOWN_KEY_FILE"
echo "$(date): Baseline key saved: $current_key"
fi
known_key=$(cat "$KNOWN_KEY_FILE")
if [ "$current_key" != "$known_key" ]; then
echo "$(date): PUBLIC KEY CHANGED — was $known_key, now $current_key" >> /var/log/yggdrasil-key.log
exit 1
fi
curl -s "$HEARTBEAT_URL" --max-time 10
Run daily. A Heartbeat monitor with 25-hour interval will alert if the check stops (key changed or script failed).
Step 6: Monitor external peer reachability (for public Yggdrasil nodes)
If your node accepts incoming peer connections on a public port (typically TCP 443 or a custom port), verify that port is reachable:
In Vigilmon: Monitors → New Monitor → TCP Port
- Host: your node's public hostname or IP
- Port: your configured Yggdrasil listen port (e.g.,
443) - Name:
Yggdrasil Public Peer Port
This catches firewall rule drift, NAT configuration changes, and host unreachability before your peers notice the disconnect.
Step 7: Configure alerting
Structure escalation by failure severity:
- Peer Connectivity — immediate alert; zero peers = isolated node, all services unreachable
- Service Health — immediate for production services
- Key Integrity — immediate; key change breaks all existing references
- Self-Address Reachability — 2-minute delay; transient interface issues can self-resolve
- Public Peer Port — 5-minute delay; brief unreachability is common during reboots
In Vigilmon: Alert Channels → Add Channel → Slack (#yggdrasil-ops) and Email for the key change monitor specifically — that one warrants immediate human attention.
Step 8: Create an overlay network status page
Aggregate Yggdrasil health metrics into a shared status page:
- Status Pages → New Status Page
- Name:
Yggdrasil Mesh Network - Add monitors: Peer Connectivity, Self Reachability, Service Health, Public Peer Port
- Publish URL to
#infra
Complete monitoring reference
| Monitor | Type | What it catches | |---------|------|-----------------| | Peer Connectivity | Heartbeat | Node isolated (zero peers) | | Self-Address Reachability | Heartbeat | Interface down, routing stack broken | | Service Health | Heartbeat | Application-layer failures | | Key Integrity | Heartbeat | Accidental key regeneration | | Public Peer Port | TCP | Node unreachable from internet |
Troubleshooting common Yggdrasil failures
- Zero peers (node isolated) — check
yggdrasilctl getPeersmanually; if all peers show"up": false, verify TCP connectivity to the configured peer addresses. Common causes: peer host down, firewall blocking the Yggdrasil port, or ISP blocking outbound TCP 443. - Self-address ping fails — run
ip -6 addr showto verify the Yggdrasil interface has an address in200::/7; if the interface is missing, restart the daemon withsystemctl restart yggdrasil. - Service unreachable over Yggdrasil — confirm the service is bound to the Yggdrasil IPv6 address (not
[::]or0.0.0.0); checkss -tlnp6on the service host. - Key change detected — if accidental, restore from backup config and restart Yggdrasil; the old key's derived IPv6 address will return. Update the baseline key file only after confirming the new key is intentional.
Next steps
- Multi-node dashboards — deploy the probe script on each Yggdrasil node and create separate heartbeat monitors per node for granular isolation detection
- Bandwidth monitoring — parse
yggdrasilctl getSelftraffic counters in the probe script and log throughput trends to detect relay saturation
Start monitoring your Yggdrasil mesh network today at vigilmon.online — free, no credit card required.