juanfont/headscale · critical

version check: %w

Error message

version check: %w

What it means

Returned by NewHeadscaleDatabase when checkVersionUpgradePath rejects the existing database version before any migration runs. Headscale only supports specific upgrade paths (e.g. pre-v0.25 databases must first be upgraded to v0.25.1, per the comment above the migration list); downgrades and multi-step jumps are refused to protect schema integrity.

Source

Thrown at hscontrol/db/db.go:63

type HSDatabase struct {
	DB  *gorm.DB
	cfg *types.Config
}

// NewHeadscaleDatabase creates a new database connection and runs migrations.
// It accepts the full configuration to allow migrations access to policy settings.
//
//nolint:gocyclo // complex database initialization with many migrations
func NewHeadscaleDatabase(cfg *types.Config) (*HSDatabase, error) {
	dbConn, err := openDB(cfg.Database)
	if err != nil {
		return nil, err
	}

	err = checkVersionUpgradePath(dbConn)
	if err != nil {
		return nil, fmt.Errorf("version check: %w", err)
	}

	migrations := gormigrate.New(
		dbConn,
		gormigrate.DefaultOptions,
		[]*gormigrate.Migration{
			// New migrations must be added as transactions at the end of this list.
			// Migrations start from v0.25.0. If upgrading from v0.24.x or earlier,
			// you must first upgrade to v0.25.1 before upgrading to this version.

			// v0.25.0
			{
				// Add a constraint to routes ensuring they cannot exist without a node.
				ID: "202501221827",
				Migrate: func(tx *gorm.DB) error {
					// Remove any invalid routes associated with a node that does not exist.
					if tx.Migrator().HasTable(&types.Route{}) && tx.Migrator().HasTable(&types.Node{}) { //nolint:staticcheck // SA1019: Route kept for migrations
						err := tx.Exec("delete from routes where node_id not in (select id from nodes)").Error

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the wrapped message to see the database's current version vs the supported range.
  2. To upgrade from pre-v0.25: first run v0.25.1 against the database, then the target version.
  3. To downgrade: restore a database backup taken before the upgrade - headscale will not migrate backwards.
  4. Never point two major versions at the same database file.
Defensive patterns

Strategy: try-catch

Try / catch

hsdb, err := db.NewHeadscaleDatabase(cfg)
if err != nil {
    if strings.Contains(err.Error(), "version check") {
        // database version is unsupported: stop, back up, and upgrade stepwise
        log.Fatal().Err(err).Msg("unsupported database version - upgrade via the documented path, do not delete the DB")
    }
    return err
}

Prevention

When it happens

Trigger: Starting a new headscale binary against a database written by a much older release (pre-v0.25), or pointing a newer binary at a database created by a NEWER version (downgrade). The wrapped error from versioncheck.go names the offending version.

Common situations: Rolling back a headscale upgrade while keeping the same database; restoring an old database backup onto a new binary; skipping several releases in one upgrade.

Related errors


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