juanfont/headscale · error

reading IPv4 addresses from database: %w

Error message

reading IPv4 addresses from database: %w

What it means

The IP allocator constructor builds the set of already-used IPv4 addresses by plucking the ipv4 column of every node row (inside a read transaction via db.Read). This error means that SELECT failed — connection problems, or the nodes table missing the ipv4 column (schema drift).

Source

Thrown at hscontrol/db/ip.go:79

) (*IPAllocator, error) {
	ret := IPAllocator{
		prefix4: prefix4,
		prefix6: prefix6,

		strategy: strategy,
	}

	var (
		v4s []sql.NullString
		v6s []sql.NullString
	)

	if db != nil {
		err := db.Read(func(rx *gorm.DB) error {
			return rx.Model(&types.Node{}).Pluck("ipv4", &v4s).Error
		})
		if err != nil {
			return nil, fmt.Errorf("reading IPv4 addresses from database: %w", err)
		}

		err = db.Read(func(rx *gorm.DB) error {
			return rx.Model(&types.Node{}).Pluck("ipv6", &v6s).Error
		})
		if err != nil {
			return nil, fmt.Errorf("reading IPv6 addresses from database: %w", err)
		}
	}

	var ips netipx.IPSetBuilder

	// Add network and broadcast addrs to used pool so they
	// are not handed out to nodes.
	if prefix4 != nil {
		network4, broadcast4 := util.GetIPPrefixEndpoints(*prefix4)
		ips.Add(network4)
		ips.Add(broadcast4)

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Verify schema and binary versions match — run the newest headscale once so migrations add any missing columns.
  2. Check DB connectivity and restart postgres if needed; retry startup.
  3. On sqlite, remove concurrent writers so reads are not blocked.
Defensive patterns

Strategy: try-catch

Try / catch

// When embedding headscale's db package:
//   alloc, err := db.NewIPAllocator(hdb, prefix4, prefix6, strategy)
//   if err != nil {
//       if strings.Contains(err.Error(), "reading IPv4 addresses") {
//           // schema/connection issue: verify migrations ran, then retry once
//       }
//       return err
//   }

Prevention

When it happens

Trigger: NewIPAllocator called at startup or during IP backfill when the nodes.ipv4 column does not exist (DB schema older than the code expects), the database connection is broken, or a read lock blocks the query on sqlite.

Common situations: Binary/database version mismatch after a botched upgrade; postgres restarted out from under headscale; concurrent long write transaction on sqlite causing busy timeouts.

Related errors


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