Skip to content

Browser SSH stuck at "Joining Tailnet" with Headscale 0.29.1 — GET /ts2021 no longer allowed #573

Description

@tomas1pit

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:

  1. Coordinate with Headscale to restore GET /ts2021 support (submit a PR to juanfont/headscale)
  2. Document that browser SSH requires Headscale 0.28 specifically, not "0.28 or newer"
  3. 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

Metadata

Metadata

Assignees

Labels

BugSomething isn't workingUpstreamCaused by changes in Headscale or other upstream tools

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions