juanfont/headscale · error
allocating IPv4 for node(%d): %w
Error message
allocating IPv4 for node(%d): %w
What it means
During backfill, a node lacks an IPv4 address while an IPv4 prefix is configured, so the allocator tries allocateNext. This error means the allocator could not find a free address in the prefix — most commonly the prefix is exhausted relative to the number of registered nodes. The transaction aborts, so no half-backfilled state persists.
Source
Thrown at hscontrol/db/ip.go:332
return fmt.Errorf("backfilling IPs: %w", errIPAllocatorNil)
}
log.Trace().Caller().Msgf("starting to backfill IPs")
nodes, err := ListNodes(tx)
if err != nil {
return fmt.Errorf("listing nodes to backfill IPs: %w", err)
}
for _, node := range nodes {
log.Trace().Caller().EmbedObject(node).Msg("ip backfill check started because node found in database")
changed := false
// IPv4 prefix is set, but node ip is missing, alloc
if i.prefix4 != nil && node.IPv4 == nil {
ret4, err := i.allocateNext(&i.prev4, i.prefix4)
if err != nil {
return fmt.Errorf("allocating IPv4 for node(%d): %w", node.ID, err)
}
node.IPv4 = ret4
changed = true
ret = append(ret, fmt.Sprintf("assigned IPv4 %q to Node(%d) %q", ret4.String(), node.ID, node.Hostname))
}
// IPv6 prefix is set, but node ip is missing, alloc
if i.prefix6 != nil && node.IPv6 == nil {
ret6, err := i.allocateNext(&i.prev6, i.prefix6)
if err != nil {
return fmt.Errorf("allocating IPv6 for node(%d): %w", node.ID, err)
}
node.IPv6 = ret6
changed = true
View on GitHub (pinned to 565fd254d0)
Solutions
- Enlarge prefixes.v4 (e.g. move from /24 to /23 or the 100.64.0.0/10 CGNAT range) and restart
- Prune expired/obsolete nodes to free addresses
- Verify the allocator is rebuilt from current DB state on startup rather than reusing stale prev4
Example fix
# before prefixes: v4: 10.1.0.0/28 # after prefixes: v4: 10.1.0.0/23
Defensive patterns
Strategy: validation
Validate before calling
// Estimate capacity before backfill: usable hosts must exceed node count
usable := prefixBitsAvailable(cfg.Prefix4) // e.g. 2^(32-mask)-2
nodeCount, _ := db.CountNodes()
if usable <= nodeCount {
return fmt.Errorf("IPv4 prefix %s too small for %d nodes", cfg.Prefix4, nodeCount)
} Type guard
func prefixHasCapacity(p netip.Prefix, nodes int) bool {
if !p.IsValid() || p.IsSingleIP() {
return false
}
size := p.Bits()
hosts := 1 << uint(32-size)
return hosts-2 > nodes
} Prevention
- Size prefixes for expected growth at install time (CGNAT /10 gives room)
- Monitor allocated-address counts vs prefix size
- Prune expired nodes regularly
When it happens
Trigger: prefix4 sized smaller than the node count (e.g. a /24 with >253 nodes when infrastructure addresses are excluded); leaked addresses from nodes deleted in the DB but still tracked by the allocator's prev4 state; a node registered while backfill holds a stale cursor.
Common situations: Operator set a tiny prefix like 10.0.0.0/28 for testing, then registered more nodes; growth past the planned address space; allocator state desynchronized after restoring a database from backup.
Related errors
- backfilling IPs: %w
- allocating IPv6 for node(%d): %w
- saving node(%d) after adding IPs: %w
- allocating IPs: %w
- failed to allocate IP
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/dea5db255c2995dd.
Report an issue: GitHub.