This guide walks you through enrolling client devices to the Charon VPN mesh using Headscale.
Before enrolling a client device:
- Headscale deployed and accessible via external ingress
- Tailscale client installed on your device
- Pre-auth key generated (or use web UI for enrollment)
Use a pre-auth key for one-command enrollment:
# Generate a reusable pre-auth key (90-day expiration)
kubectl exec -n dev headscale-0 -- headscale preauthkeys create \
--user default --reusable --expiration 90d
# Connect your device using the key
tailscale up --login-server https://vpn.example.com --authkey <your-key-here>Advantages:
- One command to connect
- Reusable for multiple devices
- No manual approval needed
For interactive enrollment (useful for user-managed devices):
# Connect without a key to trigger web enrollment
tailscale up --login-server https://vpn.example.comThis will:
-
Open your browser to the Headscale web UI
-
Show you the enrollment request
-
Require manual approval via kubectl:
# List pending nodes kubectl exec -n dev headscale-0 -- headscale nodes list # Register the node kubectl exec -n dev headscale-0 -- headscale nodes register \ --user default --key <node-key>
Advantages:
- More control over approvals
- See device details before accepting
- Audit trail of enrollments
After enrollment:
# Check Tailscale status on your device
tailscale status
# Verify node is registered in Headscale
kubectl exec -n dev headscale-0 -- headscale nodes list
# Test connectivity to a service
ping grafana.example.com # Should resolve to 100.64.0.X
curl https://grafana.example.com # Should work if on VPNSymptoms: Connection timeout when trying to enroll
Solutions:
- Check external ingress LoadBalancer has public IP
- Verify DNS records point to LoadBalancer IP
- Check firewall allows HTTPS (443) to LoadBalancer
# Check LoadBalancer IP
kubectl get svc -n ingress-nginx-external
# Test DNS resolution
dig vpn.example.com
# Test HTTPS connectivity
curl -I https://vpn.example.com/healthSymptoms: Device enrolled but shows offline in headscale nodes list
Solutions:
- Check firewall allows UDP 41641 (WireGuard)
- Check firewall allows DERP connections (various ports)
- Verify Tailscale daemon is running on device
# On the device
sudo tailscale status
sudo tailscale ping <another-node-ip>
# Check firewall (example for UFW)
sudo ufw allow 41641/udpSymptoms: VPN connected, but can't access services by hostname
Solutions:
- Verify DNS is working (MagicDNS should be enabled)
- Check service hostnames resolve to VPN IPs
- Verify services are actually running
# Check MagicDNS is enabled
tailscale status # Should show "MagicDNS: enabled"
# Test DNS resolution
dig grafana.example.com # Should return 100.64.0.X
# Check if service is reachable by IP
curl http://100.64.0.X:443 # Direct IP access (replace X with actual IP)For temporary access or single devices:
# Create a key for a specific device (non-reusable, 7-day expiration)
kubectl exec -n dev headscale-0 -- headscale preauthkeys create \
--user default --expiration 7dkubectl exec -n dev headscale-0 -- headscale nodes list --user defaultExample output:
ID | Name | NodeKey | IPAddresses | Online
---|--------------------|---------|---------------------|-------
1 | laptop-work | [abc] | 100.64.0.1 | true
2 | phone-personal | [def] | 100.64.0.2 | true
3 | grafana.example | [ghi] | 100.64.0.3 | true
# Get the node ID from list above
kubectl exec -n dev headscale-0 -- headscale nodes delete --identifier <node-id>
# Or by node key
kubectl exec -n dev headscale-0 -- headscale nodes delete --key <node-key>Generate keys ahead of time for field deployment:
# Generate 10 keys with 30-day expiration
for i in {1..10}; do
kubectl exec -n dev headscale-0 -- headscale preauthkeys create \
--user default --expiration 30d
done > preauth-keys.txtCreate a script for easy device enrollment:
#!/bin/bash
# enroll-device.sh
HEADSCALE_URL="https://vpn.example.com"
AUTH_KEY="your-preauth-key-here"
# Install Tailscale if not present
if ! command -v tailscale &> /dev/null; then
echo "Installing Tailscale..."
curl -fsSL https://tailscale.com/install.sh | sh
fi
# Connect to VPN
sudo tailscale up --login-server "$HEADSCALE_URL" --authkey "$AUTH_KEY"
# Verify connection
tailscale status- Protect pre-auth keys - Treat them like passwords
- Use short expiration - 7-90 days depending on use case
- Monitor enrollments - Regularly review
headscale nodes list - Revoke compromised keys - Delete nodes if key is exposed
- Use per-device keys - For production, avoid reusable keys
Navigation: 📚 Documentation Index | 🏠 Home