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

  1. Verify the client tailscale version supports Noise/ts2021 (all modern versions do)
  2. Ensure the /ts2021 endpoint is reachable without an interfering proxy or buffering middleware
  3. Check server logs for the wrapped error to distinguish protocol failure from network failure
  4. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/064991124d05f861. Report an issue: GitHub.