juanfont/headscale · error
allocating IPv6 for node(%d): %w
Error message
allocating IPv6 for node(%d): %w
What it means
The IPv6 mirror of the IPv4 backfill error: a node is missing an IPv6 address, prefix6 is configured, and allocateNext over prefix6 fails. In practice the IPv6 prefix is a large ULA (fd00::/8 based) so exhaustion is rare; when seen, it points at a misconfigured tiny prefix or allocator state drift. The transaction rolls back.
Source
Thrown at hscontrol/db/ip.go:345
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
ret = append(ret, fmt.Sprintf("assigned IPv6 %q to Node(%d) %q", ret6.String(), node.ID, node.Hostname))
}
// IPv4 prefix is not set, but node has IP, remove
if i.prefix4 == nil && node.IPv4 != nil {
ret = append(ret, fmt.Sprintf("removing IPv4 %q from Node(%d) %q", node.IPv4.String(), node.ID, node.Hostname))
node.IPv4 = nil
changed = true
}
// IPv6 prefix is not set, but node has IP, remove
if i.prefix6 == nil && node.IPv6 != nil {
ret = append(ret, fmt.Sprintf("removing IPv6 %q from Node(%d) %q", node.IPv6.String(), node.ID, node.Hostname))View on GitHub (pinned to 565fd254d0)
Solutions
- Set prefix6 to a standard-size ULA such as fd7a:115c:a1e0::/48
- Verify no duplicate IPv6 assignments block the cursor
- Restart headscale so the allocator is rebuilt from persisted node IPs
Example fix
# before prefixes: v6: fd00::/126 # after prefixes: v6: fd7a:115c:a1e0::/48
Defensive patterns
Strategy: validation
Validate before calling
// Use a standard ULA; reject masks longer than /64 for v6
if p6, _ := netip.ParsePrefix(cfg.Prefix6); p6.Bits() > 64 {
return fmt.Errorf("prefix6 %s too small; use a /48 or shorter ULA", cfg.Prefix6)
} Type guard
func isValidULAPrefix(p netip.Prefix) bool {
return p.IsValid() && p.Addr().Is6() && p.Bits() <= 64 && p.Addr().IsPrivate()
} Prevention
- Default to fd7a:115c:a1e0::/48-style ULAs
- Validate prefix masks in config linting before boot
When it happens
Trigger: prefix6 configured as something extremely small (e.g. fd00::/126); allocator prev6 cursor desynced after a restore; unusually high node count combined with an undersized ULA.
Common situations: Copy-paste of an IPv4-sized mask onto the v6 prefix; partial database restore losing assigned-IP records.
Related errors
- allocating IPv4 for node(%d): %w
- reading IPv6 addresses from database: %w
- allocating IPv6 address: %w
- backfilling IPs: %w
- saving node(%d) after adding IPs: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/dd44587fbeabeb48.
Report an issue: GitHub.