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

  1. Set prefix6 to a standard-size ULA such as fd7a:115c:a1e0::/48
  2. Verify no duplicate IPv6 assignments block the cursor
  3. 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

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


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