immich-app/immich · error

Server health check failed, no admin exists.

Error message

Server health check failed, no admin exists.

What it means

After restoring a database backup, the service runs migrations and then performs a health check that requires at least one admin user to exist. If userRepository.hasAdmin() returns false, the restore is considered failed (thrown inside the try block, so a rollback is triggered) because a backup without an admin account cannot produce a usable server.

Solutions

  1. Restore a backup taken from a fully initialized Immich instance (setup completed, admin created).
  2. Register an admin user after restore, or re-restore with a complete backup.
  3. Verify the dump includes the users table with an admin entry before restoring.
Defensive patterns

Strategy: try-catch

Validate before calling

const users = await api.getAllUsers();
if (!users.some((u) => u.isAdmin)) throw new Error('Backup has no admin user; restore would fail');

Try / catch

try {
  await api.restoreDatabaseBackup(filename);
} catch (e) {
  if (/no admin exists/.test(e.message)) {
    console.error('Backup predates admin creation; restore a complete backup and re-run setup.');
  } else throw e;
}

Prevention

When it happens

Trigger: Restoring a database dump that contains no admin user — e.g. a backup taken before initial setup, an empty/partial dump, or a dump from a fresh database where migrations ran but the setup wizard was never completed.

Common situations: Restoring backups copied from another instance's pre-setup state; manually imported SQL dumps; backups from test environments with no registered user.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/019628975cfc3f82. Report an issue: GitHub.

Appendix: source

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

      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 e55ac299a4)