Headscale - Hardened Self-Hosted Mesh VPN Coordinator
Autor: Lynxroute
Headscale - CIS Level 1 hardened on Ubuntu 24.04 LTS with SBOM and CIS Conformance Report.
What is Headscale
Headscale is the open-source, self-hosted implementation of the Tailscale coordination server. It speaks the same control protocol as Tailscale's hosted SaaS, so any standard Tailscale client (Linux, macOS, Windows, iOS, Android) can register against your server and join a private WireGuard-based mesh VPN. Headscale supports MagicDNS, ACLs, pre-auth keys, ephemeral nodes, route advertisement, and tag-based authorization. Written in Go as a single statically linked binary, BSD-3-Clause license.
Why self-host the coordination server
Self-hosting keeps node metadata, auth flows, and ACL evaluation inside your own tenant - none of it transits a third-party SaaS endpoint. Ideal for teams with data residency requirements (GDPR, HIPAA, ISO 27001), private mesh networks for engineering or operations, and organisations that need predictable cost and full control over peer registration.
What this VM image adds
Security hardening:
- Noise protocol private key generated per instance - never reused across deployments, written to /var/lib/headscale at first boot
- Default user and reusable 24-hour pre-auth key created automatically - run tailscale up on a client and join the mesh; no manual headscale users create / preauthkeys create dance
- gRPC API and Prometheus metrics bound to 127.0.0.1 only - never reachable from outside the VM
- Headscale runs as non-root - dedicated headscale system user, no shell, locked home directory, UMask 0027 enforced via systemd
- CVE scan - every image is scanned for vulnerabilities with Trivy before release
- UFW firewall - only the coordination endpoint (TCP 8080) is exposed; SSH on 22 only
- fail2ban - SSH brute-force protection
- AppArmor - mandatory access control
OS hardening (CIS Level 1):
- CIS Level 1 hardened - CIS Ubuntu 24.04 LTS Level 1 Benchmark via ansible-lockdown
- auditd - system call auditing for critical paths
- SSH hardening - PasswordAuthentication disabled, key-only access
- Kernel hardening - SYN cookies, ASLR, rp_filter, TCP BBR
- /tmp as tmpfs - nosuid, nodev, noexec
- Azure IMDS endpoints - egress rules pre-configured (169.254.169.254, 168.63.129.16)
Compliance artifacts (inside the VM):
- SBOM - CycloneDX 1.6 at /etc/lynxroute/sbom.json with SHA-256 hash of the Headscale binary and NTIA-compliant supplier metadata for every component
- CIS Conformance Report - OpenSCAP HTML at /etc/lynxroute/cis-report.html
- Tailored CIS profile - /usr/share/doc/lynxroute/CIS_TAILORED_PROFILE.md
- Server credentials file - /root/headscale-credentials.txt with public IP, server URL, default user ID, and 24-hour reusable pre-auth key
Quick Start
- Deploy VM from Azure Marketplace (Standard_D2s_v3 or larger recommended)
- Open NSG: TCP 8080 from the networks where your Tailscale clients live - SSH 22 from your management IPs only
- SSH: ssh -i key.pem <username>@<PUBLIC_IP> (username set during VM creation, default: azureuser)
- Read connection details: sudo cat /root/headscale-credentials.txt - contains server URL, default user ID, and 24-hour pre-auth key
- On a Tailscale client, register against the server URL: tailscale up --login-server=http://<PUBLIC_IP>:8080 --authkey=<KEY>
- Verify on the server: sudo headscale nodes list
For production, place an HTTPS reverse proxy (Application Gateway with managed cert, or Nginx with Let's Encrypt) in front of port 8080 and set server_url in /etc/headscale/config.yaml to the public HTTPS URL before any client registers.