juanfont/headscale · error

failed to allocate IP

Error message

failed to allocate IP

What it means

Exported sentinel from hscontrol/db/ip.go returned by IPAllocator.allocateNext (ip.go:227, ip.go:238) when the address pool is exhausted: sequential allocation walks past the end of the prefix, random allocation wraps and returns to its starting address. Every candidate was either already used, reserved for Tailscale infrastructure, or outside the prefix. It surfaces from SaveNodeIP-style paths (db/node.go:672) when registering a node.

Source

Thrown at hscontrol/db/ip.go:164

	if i.prefix4 != nil {
		ret4, err = i.allocateNext(&i.prev4, i.prefix4)
		if err != nil {
			return nil, nil, fmt.Errorf("allocating IPv4 address: %w", err)
		}
	}

	if i.prefix6 != nil {
		ret6, err = i.allocateNext(&i.prev6, i.prefix6)
		if err != nil {
			return nil, nil, fmt.Errorf("allocating IPv6 address: %w", err)
		}
	}

	return ret4, ret6, nil
}

var ErrCouldNotAllocateIP = errors.New("failed to allocate IP")

// allocateNext allocates the next address from prefix under i.mu, advancing
// prev so a run of allocations (e.g. BackfillNodeIPs) does not rescan
// already-issued addresses, and so prev is read under the lock rather than in
// the caller's frame.
func (i *IPAllocator) allocateNext(prev *netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) {
	i.mu.Lock()
	defer i.mu.Unlock()

	ret, err := i.next(*prev, prefix)
	if err != nil {
		return nil, err
	}

	*prev = *ret

	return ret, nil
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Enlarge ip_prefixes in config.yaml (e.g. 100.64.0.0/10, the Tailscale default) and restart headscale
  2. Delete expired/ephemeral nodes holding addresses (`headscale nodes list`, then `headscale nodes delete`)
  3. Verify prefix size with a quick count of allocated IPs vs prefix capacity before scaling the tailnet

Example fix

# before
prefixes:
  v4: 100.64.0.0/24

# after
prefixes:
  v4: 100.64.0.0/10
Defensive patterns

Strategy: validation

Validate before calling

// capacity check before mass-enrolling nodes
func capacityLeft(prefix netip.Prefix, used int) int {
    total := 1 << (prefix.Bits() // host bits)
    usable := total - reservedCount(prefix) // network/bcast + tailscale-reserved
    return usable - used
}

Try / catch

node, _, err := hsdb.SaveNodeIP(...)
if err != nil {
    if errors.Is(err, db.ErrCouldNotAllocateIP) {
        // not retryable at the same prefix: widen prefixes or prune nodes
        return fmt.Errorf("IP pool exhausted; enlarge ip_prefixes or delete unused nodes: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: prefixes.v4 (e.g. 100.64.0.0/10) or prefixes.v6 fully allocated — registering a new node finds no free address. Also triggered in unit tests with a /30 prefix after all addresses are handed out (see db/ip_random_exhaustion_test.go).

Common situations: Using a too-small prefix such as 100.64.0.0/24 or /28 for a large tailnet; address leaks from many ephemeral test nodes never expired; misconfigured prefixes after initial setup.

Related errors


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