{"record":{"id":"dd44587fbeabeb48","repo":"juanfont/headscale","slug":"allocating-ipv6-for-node-d-w","errorCode":null,"errorMessage":"allocating IPv6 for node(%d): %w","messagePattern":"allocating IPv6 for node\\((.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/ip.go","lineNumber":345,"sourceCode":"\t\t\tchanged := false\n\t\t\t// IPv4 prefix is set, but node ip is missing, alloc\n\t\t\tif i.prefix4 != nil && node.IPv4 == nil {\n\t\t\t\tret4, err := i.allocateNext(&i.prev4, i.prefix4)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"allocating IPv4 for node(%d): %w\", node.ID, err)\n\t\t\t\t}\n\n\t\t\t\tnode.IPv4 = ret4\n\t\t\t\tchanged = true\n\n\t\t\t\tret = append(ret, fmt.Sprintf(\"assigned IPv4 %q to Node(%d) %q\", ret4.String(), node.ID, node.Hostname))\n\t\t\t}\n\n\t\t\t// IPv6 prefix is set, but node ip is missing, alloc\n\t\t\tif i.prefix6 != nil && node.IPv6 == nil {\n\t\t\t\tret6, err := i.allocateNext(&i.prev6, i.prefix6)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"allocating IPv6 for node(%d): %w\", node.ID, err)\n\t\t\t\t}\n\n\t\t\t\tnode.IPv6 = ret6\n\t\t\t\tchanged = true\n\n\t\t\t\tret = append(ret, fmt.Sprintf(\"assigned IPv6 %q to Node(%d) %q\", ret6.String(), node.ID, node.Hostname))\n\t\t\t}\n\n\t\t\t// IPv4 prefix is not set, but node has IP, remove\n\t\t\tif i.prefix4 == nil && node.IPv4 != nil {\n\t\t\t\tret = append(ret, fmt.Sprintf(\"removing IPv4 %q from Node(%d) %q\", node.IPv4.String(), node.ID, node.Hostname))\n\t\t\t\tnode.IPv4 = nil\n\t\t\t\tchanged = true\n\t\t\t}\n\n\t\t\t// IPv6 prefix is not set, but node has IP, remove\n\t\t\tif i.prefix6 == nil && node.IPv6 != nil {\n\t\t\t\tret = append(ret, fmt.Sprintf(\"removing IPv6 %q from Node(%d) %q\", node.IPv6.String(), node.ID, node.Hostname))","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/ip.go#L327-L363","documentation":"The IPv6 mirror of the IPv4 backfill error: a node is missing an IPv6 address, prefix6 is configured, and allocateNext over prefix6 fails. In practice the IPv6 prefix is a large ULA (fd00::/8 based) so exhaustion is rare; when seen, it points at a misconfigured tiny prefix or allocator state drift. The transaction rolls back.","triggerScenarios":"prefix6 configured as something extremely small (e.g. fd00::/126); allocator prev6 cursor desynced after a restore; unusually high node count combined with an undersized ULA.","commonSituations":"Copy-paste of an IPv4-sized mask onto the v6 prefix; partial database restore losing assigned-IP records.","solutions":["Set prefix6 to a standard-size ULA such as fd7a:115c:a1e0::/48","Verify no duplicate IPv6 assignments block the cursor","Restart headscale so the allocator is rebuilt from persisted node IPs"],"exampleFix":"# before\nprefixes:\n  v6: fd00::/126\n\n# after\nprefixes:\n  v6: fd7a:115c:a1e0::/48","handlingStrategy":"validation","validationCode":"// Use a standard ULA; reject masks longer than /64 for v6\nif p6, _ := netip.ParsePrefix(cfg.Prefix6); p6.Bits() > 64 {\n\treturn fmt.Errorf(\"prefix6 %s too small; use a /48 or shorter ULA\", cfg.Prefix6)\n}","typeGuard":"func isValidULAPrefix(p netip.Prefix) bool {\n\treturn p.IsValid() && p.Addr().Is6() && p.Bits() <= 64 && p.Addr().IsPrivate()\n}","tryCatchPattern":null,"preventionTips":["Default to fd7a:115c:a1e0::/48-style ULAs","Validate prefix masks in config linting before boot"],"tags":["go","database","ip-allocation","ipv6","capacity"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}