Skip to content

Latest commit

 

History

History
204 lines (141 loc) · 4.61 KB

File metadata and controls

204 lines (141 loc) · 4.61 KB

Headscale Service

Headscale is an open-source, self-hosted implementation of the Tailscale control server. It provides secure mesh VPN networking for all services in Charon.

Overview

Purpose: VPN control server for mesh networking Version: 0.23.0 Port: 8080 (HTTP), 50443 (gRPC) Storage: 1Gi SQLite database Access: External ingress (for enrollment) + VPN mesh

Architecture

┌─────────────────┐
│  VPN Clients    │──┐
└─────────────────┘  │
                     │
┌─────────────────┐  │    ┌──────────────┐
│  Service Pods   │──┼────│  Headscale   │
│  (Tailscale     │  │    │  Control     │
│   sidecars)     │  │    │  Server      │
└─────────────────┘  │    └──────────────┘
                     │
┌─────────────────┐  │
│  Your Devices   │──┘
└─────────────────┘

Configuration

Terraform Variables

# terraform.tfvars
headscale_enabled      = true
headscale_version      = "0.23.0"
headscale_server_url   = "https://vpn.example.com"
headscale_ip_prefix    = "100.64.0.0/10"
headscale_magic_dns    = true
headscale_storage      = "1Gi"

See Terraform Variables Reference for full details.

Features

MagicDNS

Automatic hostname resolution for VPN-connected devices:

  • Pods get hostnames like grafana.vpn.local
  • Devices get hostnames like laptop.vpn.local
  • No manual DNS configuration needed

Pre-Auth Keys

Automated service enrollment using pre-auth keys:

  • Services automatically generate their own keys
  • Keys stored in Kubernetes secrets
  • Configurable expiration (default: 90d)

Node Management

Automatic cleanup of offline nodes:

  • Post-deployment script removes stale nodes
  • Prevents VPN IP exhaustion
  • Cleans up DNS records automatically

Common Operations

View Connected Nodes

kubectl exec -n dev headscale-0 -- headscale nodes list

Output shows:

  • Node ID
  • Node name
  • IP address (100.64.0.X)
  • Online status
  • Last seen

Create Pre-Auth Key

# Create reusable key with 90-day expiration
kubectl exec -n dev headscale-0 -- headscale preauthkeys create \
  --user default --reusable --expiration 90d

Create User

kubectl exec -n dev headscale-0 -- headscale users create <username>

Delete Offline Node

kubectl exec -n dev headscale-0 -- headscale nodes delete --identifier <node-id>

View Routes

kubectl exec -n dev headscale-0 -- headscale routes list

Networking

VPN Network Range

Default: 100.64.0.0/10 (CGNAT range)

  • First node: 100.64.0.1
  • Subsequent allocations: 100.64.0.2, 100.64.0.3, etc.

Ports

  • 8080/tcp - HTTP API (internal)
  • 50443/tcp - gRPC API (internal)
  • 443/tcp - External HTTPS access (via ingress)

Ingress

Two ingress resources:

  1. External (nginx-external) - Public access for enrollment
  2. Internal (nginx) - VPN-only access for management

Troubleshooting

Pod Not Starting

kubectl describe pod headscale-0 -n dev
kubectl logs headscale-0 -n dev

Common issues:

  • PVC not binding (check storage class)
  • ConfigMap missing (check Terraform state)

Cannot Enroll Devices

# Check external ingress
kubectl get ingress -n dev headscale-external

# Verify DNS
dig vpn.example.com

# Test endpoint
curl -I https://vpn.example.com/health

Nodes Show Offline

# Check Tailscale daemon on device
sudo tailscale status

# Check Headscale sees the node
kubectl exec -n dev headscale-0 -- headscale nodes list

# Check firewall allows UDP 41641

Database Issues

# Exec into pod
kubectl exec -it headscale-0 -n dev -- sh

# Check database
sqlite3 /var/lib/headscale/db.sqlite "SELECT * FROM nodes;"

Security

  • API key auto-generated and stored in Kubernetes secret
  • External access only for enrollment (web UI)
  • All management via VPN or kubectl
  • Pre-auth keys have configurable expiration
  • TLS certificates via cert-manager

Related Documentation


Navigation: Documentation Index | Home