field noteHeadscale · Cloudflare Tunnel · Debugging

A green health check that proved nothing

A Tailscale control server behind a tunnel passed every health check while no device could register.

I put a self-hosted Headscale server (the open-source Tailscale control server) behind a Cloudflare Tunnel, so it needed no open ports. The health endpoint answered 200, the dashboard was green, and every attempt to register a device hung.

What was going on

Tailscale clients talk to the control server over a long-lived connection that starts as an HTTP request asking to upgrade to Tailscale's own control protocol. The tunnel carried ordinary requests, so the health check passed, but it did not pass that upgrade through. The one request that mattered never reached the server in a form it could use.

The fix

Serve the control server directly: a DNS-only record pointing at the host, port 443 open, and a certificate on the server itself. Registration worked straight away.

# Test the path a real client takes, not the health endpoint
tailscale up --login-server https://hs.example.com --authkey $KEY
tailscale status
A check worth scripting: register a throwaway node and confirm it shows up.

What I took from it

  • A health check proves the health endpoint works. Test the path the real clients use.
  • Anything that rewrites or proxies HTTP (tunnels, CDNs, some load balancers) needs checking against protocols that upgrade the connection.