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
- Enlarge ip_prefixes in config.yaml (e.g. 100.64.0.0/10, the Tailscale default) and restart headscale
- Delete expired/ephemeral nodes holding addresses (`headscale nodes list`, then `headscale nodes delete`)
- 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
- Size prefixes.v4 with headroom (100.64.0.0/10 supports the full tailnet range)
- Expire/delete ephemeral test nodes so addresses return to the pool
- Alert when allocated-node count approaches prefix capacity
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
- allocating IPv4 address: %w
- allocating IPv6 address: %w
- allocating IPs: %w
- STUN address not set
- initial DERPMap is empty, Headscale requires at least one en
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/48b5a96b6567008e.
Report an issue: GitHub.