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

  1. Enlarge prefixes.v4 (e.g. move from /24 to /23 or the 100.64.0.0/10 CGNAT range) and restart
  2. Prune expired/obsolete nodes to free addresses
  3. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/dea5db255c2995dd. Report an issue: GitHub.