Description
Summary
Browser SSH hangs indefinitely at "Joining Tailnet…" when using Headscale 0.29.x. The WASM client attempts GET /ts2021?X-Tailscale-Handshake=… (WebSocket upgrade), but Headscale 0.29 returns 405 Method Not Allowed with Allow: POST.
Root Cause
In Headscale 0.28, /ts2021 accepted both methods:
router.HandleFunc(ts2021UpgradePath, h.NoiseUpgradeHandler).
Methods(http.MethodPost, http.MethodGet)
In Headscale 0.29, it was changed to POST-only:
r.Post(ts2021UpgradePath, h.NoiseUpgradeHandler)
The browser WASM cannot use POST /ts2021 (TCP long-polling) — browsers can only establish persistent bidirectional connections via WebSocket, which requires GET + Upgrade: websocket. This makes browser SSH completely broken with Headscale 0.29+, and downgrade to 0.28 is not possible due to irreversible DB migrations.
Evidence
Nginx access log during SSH attempt:
GET /key?v=125 HTTP/2.0 → 200
GET /ts2021?X-Tailscale-Handshake=AH0BAG... HTTP/1.1 → 405
GET /key?v=125 HTTP/2.0 → 200
GET /ts2021?X-Tailscale-Handshake=AH0BAG... HTTP/1.1 → 405
(repeating indefinitely)
Direct test to Headscale port 8080:
curl -H 'Upgrade: websocket' -H 'Connection: Upgrade' \
http://127.0.0.1:8080/ts2021?X-Tailscale-Handshake=AAAA
→ HTTP/1.1 405 Method Not Allowed
Allow: POST
Environment
- Headplane:
0.7.0-beta.4
- Headscale:
0.29.1
- All other prerequisites met: OIDC auth, agent enabled, Tailscale SSH enabled on target node
Suggested Fix
Either:
- Coordinate with Headscale to restore
GET /ts2021 support (submit a PR to juanfont/headscale)
- Document that browser SSH requires Headscale 0.28 specifically, not "0.28 or newer"
- Explore routing the WebSocket control-plane connection through the Headplane agent as a proxy, avoiding the direct browser→Headscale constraint
Headplane Version
0.7.0-beta.4
Headscale Version
0.29.1
Description
Summary
Browser SSH hangs indefinitely at "Joining Tailnet…" when using Headscale 0.29.x. The WASM client attempts
GET /ts2021?X-Tailscale-Handshake=…(WebSocket upgrade), but Headscale 0.29 returns405 Method Not AllowedwithAllow: POST.Root Cause
In Headscale 0.28,
/ts2021accepted both methods:In Headscale 0.29, it was changed to POST-only:
The browser WASM cannot use
POST /ts2021(TCP long-polling) — browsers can only establish persistent bidirectional connections via WebSocket, which requiresGET + Upgrade: websocket. This makes browser SSH completely broken with Headscale 0.29+, and downgrade to 0.28 is not possible due to irreversible DB migrations.Evidence
Nginx access log during SSH attempt:
Direct test to Headscale port 8080:
Environment
0.7.0-beta.40.29.1Suggested Fix
Either:
GET /ts2021support (submit a PR to juanfont/headscale)Headplane Version
0.7.0-beta.4
Headscale Version
0.29.1