01The situation
Remote access across the fleet relied on SSH keys spread over many hosts. Every new person or device meant more keys to place, and removing access meant finding every copy.
Tailscale solves this with identity-based access, but the company wanted the control plane on its own infrastructure. I ran a proof of concept with Headscale, the open-source Tailscale control server.
02What it had to do
- Self-hosted control plane, no SaaS dependency for coordination.
- SSH authorised by access rules, not by keys on each machine.
- Join existing kiosks without disturbing the VPNs they already run.
- No secrets inside anything published or baked into an image.
03How it works
Check what it can do
Before deploying, I confirmed Headscale supports Tailscale SSH, access rules, MagicDNS, subnet routes and Google login over OIDC, and noted what it can't do, such as SSH session recording.
Run the control plane
Headscale runs in Docker with its own Let's Encrypt certificate. Access rules live in its database, and a web admin UI runs alongside it.
Onboard devices safely
I wrote an installer with a read-only check mode. It reports the CPU architecture, TUN support, existing WireGuard interfaces and their subnets, other VPNs and port use, and stops outright if anything already uses Tailscale's 100.64.0.0/10 range.
Install without side effects
The install adds only the tailscale0 interface: it doesn't take over DNS or routes and doesn't change IP forwarding. The auth key is passed at run time and never stored in the hosted script.
Harden the admin side
The admin laptop runs in shields-up mode: it can SSH out to devices, but nothing on the network can connect to it.
Back it up properly
Headscale's SQLite runs in WAL mode, so copying the database file silently loses recent changes. The backup uses SQLite's own online backup, with no downtime.
04What I found along the way
It looked healthy and wasn't
Behind a Cloudflare Tunnel, the health check passed but devices couldn't register. The tunnel strips the HTTP Upgrade header that Tailscale's control protocol needs. I moved Headscale to a direct, DNS-only record with its own certificate, and registration worked.
Two VPNs fighting over traffic
On the admin laptop, a full-tunnel WireGuard policy rule captured Tailscale traffic before Tailscale's own routing table saw it, so SSH timed out. One higher-priority routing rule for the Tailscale range fixed it, persisted in the WireGuard config hooks.
05What changed
- Keyless SSH worked end to end: a connection authorised only by the access rules, with no key on the target.
- Kiosks joined the network next to their existing WireGuard, OpenVPN and L2TP/IPsec tunnels with nothing broken.
- It now runs as the live access network for the kiosk fleet.
- I generalised the installer into ts-coexist, a standalone tool that checks a Linux host before joining it to Tailscale or Headscale.
06Takeaways
- A green health check proves the health endpoint works, not the service. Test the path real clients use.
- Look before you install: a read-only check mode made it safe to run on devices I didn't set up.