juanfont/headscale · critical
allocating IPs: %w
Error message
allocating IPs: %w
What it means
The IP allocator's Next() call failed while provisioning a brand-new node, after ownership validation passed. The allocator hands out one IPv4 and one IPv6 from the configured prefixes; failure almost always means the pool (or one address family) is exhausted, and the error is wrapped as "allocating IPs: %w".
Source
Thrown at hscontrol/state/state.go:2010
// Tagged nodes are exempt — they never expire.
needsDefaultExpiry := !nodeToRegister.IsTagged() &&
(nodeToRegister.Expiry == nil || nodeToRegister.Expiry.IsZero()) &&
s.cfg.Node.Expiry > 0
if needsDefaultExpiry {
exp := time.Now().Add(s.cfg.Node.Expiry)
nodeToRegister.Expiry = &exp
}
// Validate before saving
err := validateNodeOwnership(&nodeToRegister)
if err != nil {
return types.NodeView{}, err
}
// Allocate new IPs
ipv4, ipv6, err := s.ipAlloc.Next()
if err != nil {
return types.NodeView{}, fmt.Errorf("allocating IPs: %w", err)
}
nodeToRegister.IPv4 = ipv4
nodeToRegister.IPv6 = ipv6
// Seed GivenName from the sanitised raw hostname. [NodeStore.PutNode]
// bumps on collision and falls back to "node" if the sanitised
// result is empty (pure non-ASCII / punctuation input).
if nodeToRegister.GivenName == "" {
nodeToRegister.GivenName = dnsname.SanitizeHostname(nodeToRegister.Hostname)
}
// New node - database first to get ID, then [NodeStore]
savedNode, err := hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
err := tx.Save(&nodeToRegister).Error
if err != nil {
return nil, fmt.Errorf("saving node: %w", err)
}View on GitHub (pinned to 565fd254d0)
Solutions
- Widen ip_prefixes in config (e.g. 100.64.0.0/10 or a larger private CIDR) and restart headscale
- Expire/delete stale nodes with `headscale nodes expire` / `headscale nodes delete` to reclaim addresses
- Check the wrapped error text — it names which family's prefix is exhausted
Example fix
# config.yaml - before ip_prefixes: [10.0.0.0/28] # after ip_prefixes: [10.0.0.0/24, fd7a:115c:a1e0::/48]
Defensive patterns
Strategy: validation
Validate before calling
// Capacity precheck before registration:
free4, free6 := allocator.FreeCount()
if free4 == 0 || free6 == 0 {
return fmt.Errorf("IP pool exhausted (v4 free=%d, v6 free=%d); widen ip_prefixes or expire nodes", free4, free6)
} Try / catch
node, err := s.createNode(...)
if err != nil {
var allocErr *ipallocator.ErrCouldNotAllocateIP // adjust to actual allocator sentinel
if errors.As(err, &allocErr) {
// Operational: page capacity, expire stale nodes, do not retry
alertCapacity(err)
}
return err
} Prevention
- Size ip_prefixes for fleet growth (default 100.64.0.0/10 is huge; custom small CIDRs are the usual trap)
- Expire or delete decommissioned nodes so addresses recycle
- Alert when free address count drops below a threshold
When it happens
Trigger: Registering a new node when all addresses in ip_prefixes (or the IPv6 range) are assigned — e.g. default 100.64.0.0/10 near capacity, or a user-configured /24 or /25 prefix fully allocated. Happens after validateNodeOwnership but before the node is saved.
Common situations: Small custom prefix (e.g. 10.0.0.0/28) filled by ephemeral test containers; large fleet on default range; nodes never expired so addresses never recycled.
Related errors
- failed to allocate IP
- allocating IPv4 address: %w
- allocating IPv6 address: %w
- allocating IPv4 for node(%d): %w
- allocating IPv6 for node(%d): %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9129759841ff267b.
Report an issue: GitHub.