juanfont/headscale · error
allocating IPv4 address: %w
Error message
allocating IPv4 address: %w
What it means
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.
Source
Thrown at hscontrol/db/ip.go:150
)
}
ret.usedIPs = ips
return &ret, nil
}
func (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) {
var (
err error
ret4 *netip.Addr
ret6 *netip.Addr
)
if i.prefix4 != nil {
ret4, err = i.allocateNext(&i.prev4, i.prefix4)
if err != nil {
return nil, nil, fmt.Errorf("allocating IPv4 address: %w", err)
}
}
if i.prefix6 != nil {
ret6, err = i.allocateNext(&i.prev6, i.prefix6)
if err != nil {
return nil, nil, fmt.Errorf("allocating IPv6 address: %w", err)
}
}
return ret4, ret6, nil
}
var ErrCouldNotAllocateIP = errors.New("failed to allocate IP")
// allocateNext allocates the next address from prefix under i.mu, advancing
// prev so a run of allocations (e.g. BackfillNodeIPs) does not rescan
// already-issued addresses, and so prev is read under the lock rather than inView on GitHub (pinned to 565fd254d0)
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.
Example fix
# before (config.yaml) ip_prefixes: - 100.100.0.0/28 # ~14 usable, exhausted # after ip_prefixes: - 100.64.0.0/10 # default, huge pool - fd7a:115c:a1e0::/48
Defensive patterns
Strategy: fallback
Validate before calling
// Capacity planning check before adding nodes:
// usable := prefix bits -> hosts; compare with node count.
func capacityOK(prefix netip.Prefix, nodeCount int) bool {
bits := prefix.Bits()
total := new(big.Int).Lsh(big.NewInt(1), uint(prefix.Addr().BitLen()-bits))
usable := new(big.Int).Sub(total, big.NewInt(2)) // network + broadcast reserved
return big.NewInt(int64(nodeCount)).Cmp(usable) < 0
} Try / catch
// On allocation failure, fall back to enqueueing/retrying node registration // after freeing addresses (delete stale nodes) or widening the prefix. // Do not silently hand out addresses outside the prefix.
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to allocate IP
- allocating IPv6 address: %w
- allocating IPs: %w
- no IPv4 or IPv6 prefix configured, minimum one prefix is req
- reading IPv4 addresses from database: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9cb78e5cbb2a069c.
Report an issue: GitHub.