{"record":{"id":"9129759841ff267b","repo":"juanfont/headscale","slug":"allocating-ips-w","errorCode":null,"errorMessage":"allocating IPs: %w","messagePattern":"allocating IPs: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/state/state.go","lineNumber":2010,"sourceCode":"\t// Tagged nodes are exempt — they never expire.\n\tneedsDefaultExpiry := !nodeToRegister.IsTagged() &&\n\t\t(nodeToRegister.Expiry == nil || nodeToRegister.Expiry.IsZero()) &&\n\t\ts.cfg.Node.Expiry > 0\n\tif needsDefaultExpiry {\n\t\texp := time.Now().Add(s.cfg.Node.Expiry)\n\t\tnodeToRegister.Expiry = &exp\n\t}\n\n\t// Validate before saving\n\terr := validateNodeOwnership(&nodeToRegister)\n\tif err != nil {\n\t\treturn types.NodeView{}, err\n\t}\n\n\t// Allocate new IPs\n\tipv4, ipv6, err := s.ipAlloc.Next()\n\tif err != nil {\n\t\treturn types.NodeView{}, fmt.Errorf(\"allocating IPs: %w\", err)\n\t}\n\n\tnodeToRegister.IPv4 = ipv4\n\tnodeToRegister.IPv6 = ipv6\n\n\t// Seed GivenName from the sanitised raw hostname. [NodeStore.PutNode]\n\t// bumps on collision and falls back to \"node\" if the sanitised\n\t// result is empty (pure non-ASCII / punctuation input).\n\tif nodeToRegister.GivenName == \"\" {\n\t\tnodeToRegister.GivenName = dnsname.SanitizeHostname(nodeToRegister.Hostname)\n\t}\n\n\t// New node - database first to get ID, then [NodeStore]\n\tsavedNode, err := hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {\n\t\terr := tx.Save(&nodeToRegister).Error\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"saving node: %w\", err)\n\t\t}","sourceCodeStart":1992,"sourceCodeEnd":2028,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/state/state.go#L1992-L2028","documentation":"The IP allocator's Next() call failed while provisioning a brand-new node, after ownership validation passed. The allocator hands out one IPv4 and one IPv6 from the configured prefixes; failure almost always means the pool (or one address family) is exhausted, and the error is wrapped as \"allocating IPs: %w\".","triggerScenarios":"Registering a new node when all addresses in ip_prefixes (or the IPv6 range) are assigned — e.g. default 100.64.0.0/10 near capacity, or a user-configured /24 or /25 prefix fully allocated. Happens after validateNodeOwnership but before the node is saved.","commonSituations":"Small custom prefix (e.g. 10.0.0.0/28) filled by ephemeral test containers; large fleet on default range; nodes never expired so addresses never recycled.","solutions":["Widen ip_prefixes in config (e.g. 100.64.0.0/10 or a larger private CIDR) and restart headscale","Expire/delete stale nodes with `headscale nodes expire` / `headscale nodes delete` to reclaim addresses","Check the wrapped error text — it names which family's prefix is exhausted"],"exampleFix":"# config.yaml - before\nip_prefixes: [10.0.0.0/28]\n\n# after\nip_prefixes: [10.0.0.0/24, fd7a:115c:a1e0::/48]","handlingStrategy":"validation","validationCode":"// Capacity precheck before registration:\nfree4, free6 := allocator.FreeCount()\nif free4 == 0 || free6 == 0 {\n    return fmt.Errorf(\"IP pool exhausted (v4 free=%d, v6 free=%d); widen ip_prefixes or expire nodes\", free4, free6)\n}","typeGuard":null,"tryCatchPattern":"node, err := s.createNode(...)\nif err != nil {\n    var allocErr *ipallocator.ErrCouldNotAllocateIP // adjust to actual allocator sentinel\n    if errors.As(err, &allocErr) {\n        // Operational: page capacity, expire stale nodes, do not retry\n        alertCapacity(err)\n    }\n    return err\n}","preventionTips":["Size ip_prefixes for fleet growth (default 100.64.0.0/10 is huge; custom small CIDRs are the usual trap)","Expire or delete decommissioned nodes so addresses recycle","Alert when free address count drops below a threshold"],"tags":["ip-allocation","capacity","registration","config","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}