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
- Fix the prefix configuration (valid CIDRs for prefixes.v4 / prefixes.v6) so the IPAllocator is constructed
- Find the caller that passes the allocator and make it return the construction error instead of continuing with nil
- 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
- Treat IPAllocator construction failure as fatal at startup
- Never ignore the error returned when building the allocator
- Add a config-validation step that rejects empty/invalid prefixes before boot
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
- allocating IPv4 for node(%d): %w
- allocating IPv6 for node(%d): %w
- saving node(%d) after adding IPs: %w
- node not found
- must be a positive duration
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/b1e196343e726721.
Report an issue: GitHub.