immich-app/immich · critical · Error

Server health check failed, no admin exists.

Error message

Server health check failed, no admin exists.

What it means

Thrown (plain Error) inside DatabaseBackupService.restoreDatabaseBackup after migrations run. As a post-restore health check it asserts userRepository.hasAdmin() is true; an empty (no-admin) restored DB means the restore did not yield a usable system, so it triggers the rollback path (restoring the pre-restore snapshot).

Source

Thrown at server/src/services/database-backup.service.ts:420

      const [progressSource, progressSink] = createSqlProgressStreams((progress) => {
        if (isComplete) {
          return;
        }

        this.logger.log(`Restore progress ~ ${(progress * 100).toFixed(2)}%`);
        progressCb?.('restore', progress);
      });

      await pipeline(sqlStream, createSqlOwnerTransformStream(databaseUsername), progressSource, psql, progressSink);

      try {
        progressCb?.('migrations', 0.9);

        await this.databaseRepository.runMigrations();

        const hasAdmin = await this.userRepository.hasAdmin();
        if (!hasAdmin) {
          throw new Error('Server health check failed, no admin exists.');
        }

        await this.maintenanceHealthRepository.checkApiHealth();
      } catch (error) {
        progressCb?.('rollback', 0);

        const fileStream = this.storageRepository.createPlainReadStream(restorePointFilePath);
        const gunzip = this.storageRepository.createGunzip();
        fileStream.pipe(gunzip);
        inputStream = gunzip;

        const sqlStream = Readable.from(sqlRollback(inputStream, databaseUsername));
        const psql = this.processRepository.spawnDuplexStream(bin, args, {
          env: {
            PATH: process.env.PATH,
            PGPASSWORD: databasePassword,
          },
        });

View on GitHub (pinned to 199723261c)

Solutions

  1. Restore from a known-good backup that was taken from a fully set-up instance (with an admin user).
  2. If no such backup exists, start fresh, complete onboarding to create an admin, then take a backup.
  3. Inspect the dump before restoring to confirm the users table contains an isAdmin=true row.

Example fix

// before
await backupService.restoreDatabaseBackup(file);

// after
// Validate the source dump is non-empty and contains an admin before attempting restore.
const preview = await inspectDump(path.join(BACKUP_DIR, file));
if (!preview.hasAdminUser) {
  throw new Error('Refusing to restore: backup contains no admin user; pick a complete backup.');
}
await backupService.restoreDatabaseBackup(file);
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-flight: confirm the dump contains an admin before restoring.
const preview = await inspectDump(path.join(BACKUP_DIR, filename));
if (!preview.hasAdminUser) {
  throw new Error('Backup has no admin user; refusing restore.');
}

Try / catch

try {
  await backupService.restoreDatabaseBackup(filename, onProgress);
} catch (e) {
  if (e instanceof Error && e.message.includes('no admin exists')) {
    // service already rolled back to the snapshot; surface a clear message
    throw new Error('Restore failed: backup contained no admin; rolled back to previous state.');
  }
  throw e;
}

Prevention

When it happens

Trigger: Restoring a backup that contains no admin user — e.g. a partial/empty dump, a dump of the wrong database, or a backup taken before any admin existed.

Common situations: Restoring a dump from a non-Immich or empty schema; restoring a backup of a fresh DB that never had an admin; the migrations ran but the users table is empty.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/019628975cfc3f82. Report an issue: GitHub.