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

  1. Widen ip_prefixes in config (e.g. 100.64.0.0/10 or a larger private CIDR) and restart headscale
  2. Expire/delete stale nodes with `headscale nodes expire` / `headscale nodes delete` to reclaim addresses
  3. 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

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


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