{"record":{"id":"b441feeee864beb6","repo":"juanfont/headscale","slug":"node-key-already-in-use-by-another-machine","errorCode":null,"errorMessage":"node key already in use by another machine","messagePattern":"node key already in use by another machine","errorType":"http","errorClass":"ErrNodeKeyInUse","httpStatus":409,"severity":"error","filePath":"hscontrol/state/state.go","lineNumber":119,"sourceCode":"\t\"IPv6\",\n\t\"Hostname\",\n\t\"GivenName\",\n\t\"UserID\",\n\t\"RegisterMethod\",\n\t\"Tags\",\n\t\"Expiry\",\n\t\"LastSeen\",\n\t\"ApprovedRoutes\",\n\t\"UpdatedAt\",\n}\n\n// ErrRegistrationExpired is returned when a registration has expired.\nvar ErrRegistrationExpired = errors.New(\"registration expired\")\n\n// ErrNodeKeyInUse is returned when a registration or re-auth claims a NodeKey\n// already bound to a different machine, enforcing the 1:1 NodeKey<->MachineKey\n// binding.\nvar ErrNodeKeyInUse = errors.New(\"node key already in use by another machine\")\n\n// ErrAmbiguousNodeOwnership is returned when a machine key maps to a set of\n// nodes from which the correct one to update or convert cannot be determined:\n// multiple user-owned candidates for a tagged conversion, or a tagged node and\n// a user-owned node coexisting (impossible per validateNodeOwnership). The\n// registration is rejected rather than mutating an arbitrarily-picked node.\nvar ErrAmbiguousNodeOwnership = errors.New(\"machine key maps to ambiguous node ownership\")\n\n// sshCheckPair identifies a (source, destination) node pair for\n// SSH check auth tracking.\ntype sshCheckPair struct {\n\tSrc types.NodeID\n\tDst types.NodeID\n}\n\n// State manages Headscale's core state, coordinating between database, policy management,\n// IP allocation, and DERP routing. All methods are thread-safe.\n//","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/state/state.go#L101-L137","documentation":"ErrNodeKeyInUse is a sentinel error in hscontrol/state/state.go:119 enforcing the invariant that a NodeKey binds 1:1 to one machine. It is returned at state.go:1744, 1911 and 2602 when a registration or re-auth presents a NodeKey already bound to a different machine (different MachineKey). Covered by auth_nodekey_binding_test.go and persist_test.go; API layers translate it to a machine-key-conflict response (v1/errors.go:42).","triggerScenarios":"Registering a new machine with a NodeKey that an existing node already uses; re-authenticating after a machine-key change while keeping the old node key; restoring a node key backup onto different hardware with a new machine key.","commonSituations":"Cloning a VM/container including the tailscale state directory so both copies present the same node key; deliberately reusing a node key on new hardware; corrupted/mismatched state after disk imaging.","solutions":["Run 'tailscale logout' or delete the local state (/var/lib/tailscale) on the duplicate machine so it generates a fresh node key","Alternatively delete the existing node (headscale node delete -i <id>) that holds the key, then re-register","Never clone the tailscale state directory when imaging machines","If the machine key intentionally changed (hardware swap), delete and re-register the node"],"exampleFix":"# before: cloned VM reuses node key, registration fails\n# after: on the clone, reset identity\ntailscale logout\nrm -rf /var/lib/tailscale/tailscaled.state\nsystemctl restart tailscaled","handlingStrategy":"validation","validationCode":"null","typeGuard":"null","tryCatchPattern":"if errors.Is(err, state.ErrNodeKeyInUse) {\n    // do not retry: delete the conflicting node or reset the client's tailscale state, then re-register\n}","preventionTips":["Never clone the tailscale state directory when imaging machines","On hardware/machine-key changes, delete the old node before re-registering"],"tags":["headscale","registration","node-key","conflict","cloning"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}