{"record":{"id":"064991124d05f861","repo":"juanfont/headscale","slug":"upgrading-noise-connection-w","errorCode":null,"errorMessage":"upgrading noise connection: %w","messagePattern":"upgrading noise connection: %w","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"hscontrol/noise.go","lineNumber":124,"sourceCode":"\t\thttp.Error(writer, \"Internal error\", http.StatusInternalServerError)\n\n\t\treturn\n\t}\n\n\tns := noiseServer{\n\t\theadscale: h,\n\t\tchallenge: key.NewChallenge(),\n\t}\n\n\tnoiseConn, err := controlhttpserver.AcceptHTTP(\n\t\treq.Context(),\n\t\twriter,\n\t\treq,\n\t\t*h.noisePrivateKey,\n\t\tns.earlyNoise,\n\t)\n\tif err != nil {\n\t\thttpError(writer, fmt.Errorf(\"upgrading noise connection: %w\", err))\n\t\treturn\n\t}\n\n\tns.conn = noiseConn\n\tns.machineKey = ns.conn.Peer()\n\tns.protocolVersion = ns.conn.ProtocolVersion()\n\n\t// This router is served only over the Noise connection, and exposes only the new API.\n\t//\n\t// The HTTP2 server that exposes this router is created for\n\t// a single hijacked connection from /ts2021, using [netutil.NewOneConnListener]\n\n\tr := chi.NewRouter()\n\n\t// Limit request body size to prevent unauthenticated OOM attacks.\n\t// The Noise handshake accepts any machine key without checking\n\t// registration, so all endpoints behind this router are reachable\n\t// without credentials.","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/noise.go#L106-L142","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"noiseConn, err := controlhttpserver.AcceptHTTP(ctx, w, r, privKey, early)\nif err != nil {\n    // transient network failures may succeed on a fresh connection;\n    // protocol failures (bad frame, bad key) will not — inspect wrapped err\n    httpError(w, fmt.Errorf(\"upgrading noise connection: %w\", err))\n    return\n}","preventionTips":["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"],"tags":["noise","handshake","network","ts2021"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}