{"record":{"id":"9cb78e5cbb2a069c","repo":"juanfont/headscale","slug":"allocating-ipv4-address-w","errorCode":null,"errorMessage":"allocating IPv4 address: %w","messagePattern":"allocating IPv4 address: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/ip.go","lineNumber":150,"sourceCode":"\t\t)\n\t}\n\n\tret.usedIPs = ips\n\n\treturn &ret, nil\n}\n\nfunc (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) {\n\tvar (\n\t\terr  error\n\t\tret4 *netip.Addr\n\t\tret6 *netip.Addr\n\t)\n\n\tif i.prefix4 != nil {\n\t\tret4, err = i.allocateNext(&i.prev4, i.prefix4)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"allocating IPv4 address: %w\", err)\n\t\t}\n\t}\n\n\tif i.prefix6 != nil {\n\t\tret6, err = i.allocateNext(&i.prev6, i.prefix6)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"allocating IPv6 address: %w\", err)\n\t\t}\n\t}\n\n\treturn ret4, ret6, nil\n}\n\nvar ErrCouldNotAllocateIP = errors.New(\"failed to allocate IP\")\n\n// allocateNext allocates the next address from prefix under i.mu, advancing\n// prev so a run of allocations (e.g. BackfillNodeIPs) does not rescan\n// already-issued addresses, and so prev is read under the lock rather than in","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/ip.go#L132-L168","documentation":"IPAllocator.Next() tries to claim the next free IPv4 address from the configured prefix (sequentially or from a random start). This wrapper fires when allocateNext fails for the v4 family: the pool is exhausted, or the used-IP set could not be built/queried.","triggerScenarios":"More nodes (or reserved addresses) than fit in the ipv4 prefix — e.g. a /24 with ~250 usable addresses fully allocated; or the usedIPs IPSet() call inside allocateNext returning an error.","commonSituations":"Small custom ip_prefixes (like a /28) on a busy tailnet; default 100.64.0.0/10 is huge so exhaustion usually implies a custom narrow prefix; mass node registration via pre-auth keys or automation.","solutions":["Enlarge the IPv4 prefix in ip_prefixes (or remove the v4 prefix entirely to run v6-only).","Delete decommissioned nodes to free addresses: headscale nodes delete -I <id>.","Recount usage vs capacity: count rows in nodes against usable hosts in the prefix."],"exampleFix":"# before (config.yaml)\nip_prefixes:\n  - 100.100.0.0/28 # ~14 usable, exhausted\n\n# after\nip_prefixes:\n  - 100.64.0.0/10 # default, huge pool\n  - fd7a:115c:a1e0::/48","handlingStrategy":"fallback","validationCode":"// Capacity planning check before adding nodes:\n// usable := prefix bits -> hosts; compare with node count.\nfunc capacityOK(prefix netip.Prefix, nodeCount int) bool {\n    bits := prefix.Bits()\n    total := new(big.Int).Lsh(big.NewInt(1), uint(prefix.Addr().BitLen()-bits))\n    usable := new(big.Int).Sub(total, big.NewInt(2)) // network + broadcast reserved\n    return big.NewInt(int64(nodeCount)).Cmp(usable) < 0\n}","typeGuard":null,"tryCatchPattern":"// On allocation failure, fall back to enqueueing/retrying node registration\n// after freeing addresses (delete stale nodes) or widening the prefix.\n// Do not silently hand out addresses outside the prefix.","preventionTips":["Size ip_prefixes for expected node count plus headroom.","Periodically prune expired nodes (`headscale nodes prune` / expiry policies).","Monitor allocated-vs-capacity as a headscale metric."],"tags":["ip-allocation","ipv4","capacity","config"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}