MHSanaei/3x-ui · error

not a 3x-ui panel database: required table %q is missing

Error message

not a 3x-ui panel database: required table %q is missing

What it means

Thrown by PrepareSQLiteForMigration, the pre-flight check of the SQLite source file: it opens the file and requires the core panel tables users, settings and inbounds to exist. If any is missing the file is declared not a panel database before any downtime or copying happens. Key trap: gorm.Open(sqlite.Open(path)) silently CREATES an empty database when the path does not exist, so a typo'd path reliably produces this error.

Source

Thrown at internal/database/migrate_data.go:336

// PrepareSQLiteForMigration rejects SQLite files that are not a panel database
// before the caller causes any downtime, then AutoMigrates the panel schema
// onto the file so backups from older versions gain the newer tables and
// columns the row copy reads. Data-level upgrades are not needed here: they
// run dialect-agnostically on the destination via InitDB after the import.
func PrepareSQLiteForMigration(dbPath string) error {
	gdb, err := gorm.Open(sqlite.Open(dbPath+"?_busy_timeout=10000"), &gorm.Config{Logger: logger.Discard})
	if err != nil {
		return err
	}
	sqlDB, err := gdb.DB()
	if err != nil {
		return err
	}
	defer sqlDB.Close()

	for _, table := range []string{"users", "settings", "inbounds"} {
		if !sqliteTableExists(sqlDB, table) {
			return fmt.Errorf("not a 3x-ui panel database: required table %q is missing", table)
		}
	}
	for _, m := range migrationModels() {
		if err := gdb.AutoMigrate(m); err != nil && !isIgnorableDuplicateColumnErr(gdb, err, m) {
			return fmt.Errorf("upgrade panel schema for %T: %w", m, err)
		}
	}
	return nil
}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Verify the path and inspect the file: sqlite3 /path/x-ui.db '.tables' — users, settings, inbounds must be listed.
  2. Delete any empty SQLite file the failed run created at the wrong path, then pass the real backup path.
  3. If the backup is 0 bytes or truncated, re-take it from the source panel.
  4. Confirm you selected the panel DB (commonly /etc/x-ui/x-ui.db), not another *.db in the same directory.
Defensive patterns

Strategy: validation

Validate before calling

// verify the file is a panel DB before calling migration entry points
db, err := sql.Open("sqlite", dbPath)
if err != nil { return err }
defer db.Close()
for _, t := range []string{"users", "settings", "inbounds"} {
    var one int
    if db.QueryRow("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", t).Scan(&one) != nil {
        return fmt.Errorf("%s lacks table %s — not a panel DB", dbPath, t)
    }
}

Prevention

When it happens

Trigger: Passing a wrong/nonexistent file path (an empty DB is created, no tables); passing some other SQLite file (e.g. the sub-server DB or an unrelated app); a truncated/corrupt backup; an empty 0-byte backup file.

Common situations: Shell glob or variable expansion producing a wrong path; restoring a backup that failed mid-transfer; pointing the migration at the subscription-server DB instead of the panel DB; the file being created by a previous failed run at the same wrong path.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/856eb84e359e0c33. Report an issue: GitHub.