juanfont/headscale · error · errIPAllocatorNil

backfilling IPs: %w

Error message

backfilling IPs: %w

What it means

BackfillNodeIPs reconciles node IPs against the configured prefixes at startup. It refuses to run when the *IPAllocator argument is nil (errIPAllocatorNil), because there is no prefix configuration to backfill against. The guard fires before any database work, so no partial writes occur.

Source

Thrown at hscontrol/db/ip.go:314

// BackfillNodeIPs will take a database transaction, and
// iterate through all of the current nodes ([types.Node]) in headscale
// and ensure it has IP addresses according to the current
// configuration.
// This means that if both IPv4 and IPv6 is set in the
// config, and some nodes are missing that type of IP,
// it will be added.
// If a prefix type has been removed (IPv4 or IPv6), it
// will remove the IPs in that family from the node.
func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) {
	var (
		err error
		ret []string
	)

	err = db.Write(func(tx *gorm.DB) error {
		if i == nil {
			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)

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Fix the prefix configuration (valid CIDRs for prefixes.v4 / prefixes.v6) so the IPAllocator is constructed
  2. Find the caller that passes the allocator and make it return the construction error instead of continuing with nil
  3. Fail startup on allocator construction error rather than calling backfill with nil

Example fix

// before
alloc, _ := ipalloc.New(cfg.Prefix4, cfg.Prefix6) // error ignored, alloc may be nil
db.BackfillNodeIPs(alloc)

// after
alloc, err := ipalloc.New(cfg.Prefix4, cfg.Prefix6)
if err != nil {
	return fmt.Errorf("constructing IP allocator: %w", err)
}
db.BackfillNodeIPs(alloc)
Defensive patterns

Strategy: validation

Validate before calling

if ipAlloc == nil {
	return fmt.Errorf("cannot backfill IPs: IP allocator is nil (check prefix configuration)")
}
changes, err := db.BackfillNodeIPs(ipAlloc)

Type guard

func hasAllocator(i *db.IPAllocator) bool { return i != nil }

Prevention

When it happens

Trigger: Calling db.BackfillNodeIPs(nil), typically because the IPAllocator failed to construct (invalid or absent ipv4/ipv6 prefix in config) and the caller propagated a nil pointer instead of aborting.

Common situations: Removing or corrupting prefixes in config after previously running with them; upgrading headscale where the allocator construction path changed; prefix strings that fail netip.ParsePrefix so the caller silently keeps a nil allocator.

Related errors


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