juanfont/headscale · error
upgrading noise connection: %w
Error message
upgrading noise connection: %w
What it means
Logged by the /ts2021 handler when controlhttpserver.AcceptHTTP fails to complete the Noise protocol handshake over HTTP. AcceptHTTP validates the client's handshake frame, performs the Noise exchange, and upgrades the connection; any protocol violation, crypto mismatch, or I/O failure surfaces here.
Source
Thrown at hscontrol/noise.go:124
http.Error(writer, "Internal error", http.StatusInternalServerError)
return
}
ns := noiseServer{
headscale: h,
challenge: key.NewChallenge(),
}
noiseConn, err := controlhttpserver.AcceptHTTP(
req.Context(),
writer,
req,
*h.noisePrivateKey,
ns.earlyNoise,
)
if err != nil {
httpError(writer, fmt.Errorf("upgrading noise connection: %w", err))
return
}
ns.conn = noiseConn
ns.machineKey = ns.conn.Peer()
ns.protocolVersion = ns.conn.ProtocolVersion()
// This router is served only over the Noise connection, and exposes only the new API.
//
// The HTTP2 server that exposes this router is created for
// a single hijacked connection from /ts2021, using [netutil.NewOneConnListener]
r := chi.NewRouter()
// Limit request body size to prevent unauthenticated OOM attacks.
// The Noise handshake accepts any machine key without checking
// registration, so all endpoints behind this router are reachable
// without credentials.View on GitHub (pinned to 565fd254d0)
Solutions
- Verify the client tailscale version supports Noise/ts2021 (all modern versions do)
- Ensure the /ts2021 endpoint is reachable without an interfering proxy or buffering middleware
- Check server logs for the wrapped error to distinguish protocol failure from network failure
- Confirm system clocks are sane — Noise handshakes are sensitive to replay/malformed frames, and broken proxies often cause truncation
Defensive patterns
Strategy: retry
Try / catch
noiseConn, err := controlhttpserver.AcceptHTTP(ctx, w, r, privKey, early)
if err != nil {
// transient network failures may succeed on a fresh connection;
// protocol failures (bad frame, bad key) will not — inspect wrapped err
httpError(w, fmt.Errorf("upgrading noise connection: %w", err))
return
} Prevention
- Expose /ts2021 directly or through a pass-through proxy without buffering
- Keep client and server tailscale protocol versions current
- Log the wrapped error to distinguish transient network issues from handshake corruption
When it happens
Trigger: A client speaking a corrupted or incompatible ts2021 handshake; a machine key the server cannot parse; a proxy that buffers/breaks the HTTP upgrade; connection reset mid-handshake.
Common situations: An old or non-standard client attempting /ts2021; reverse proxies (nginx/cloudflare) not passing the upgrade through transparently; TLS-terminating middleboxes altering the framed body; port scanners sending garbage bytes.
Related errors
- MOCKOIDC_CLIENT_SECRET not defined
- health check timed out
- MOCKOIDC_PORT not defined
- command aborted by user
- --name or --identifier flag is required
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/064991124d05f861.
Report an issue: GitHub.