passbolt/passbolt_api · error · Exception
Can not upgrade. Some tables are missing.
Error message
Can not upgrade. Some tables are missing.
What it means
Thrown by Gnupg::setDecryptKey after gnupg_adddecryptkey() fails for the just-imported armored key. It means GnuPG refused to register the key as a decryption key — typically because the passphrase is wrong or the key has no usable secret key. The underlying gnupg exception message is appended to the message.
Solutions
- Verify the passphrase configured for the server key is correct (test manually with gpg).
- Ensure the armored key passed is the PRIVATE key (contains 'PRIVATE KEY BLOCK').
- Re-import/re-generate the server key and update config (passbolt.serverGpg.keyId / fingerprint).
- Inspect the appended gnupg exception message for the precise gnupg error code.
- Check gnupg keyring permissions (GNUPGHOME writable by web server user) and key expiry.
Example fix
// before $gpg->setDecryptKey($publicKey, $passphrase); // public key, no secret part // after $gpg->setDecryptKey($privateKey, $correctPassphrase);
Defensive patterns
Strategy: validation
Validate before calling
if (strpos($armoredKey, 'BEGIN PGP PRIVATE KEY BLOCK') === false) {
throw new InvalidArgumentException('setDecryptKey requires a private key');
}
Type guard
function isArmoredPrivateKey(string $s): bool {
return is_string($s) && strpos($s, 'BEGIN PGP PRIVATE KEY BLOCK') !== false;
}
Try / catch
try {
$gpg->setDecryptKey($key, $pass);
} catch (\Cake\Core\Exception\Exception $e) {
$this->log('decrypt key rejected: ' . $e->getMessage());
throw new ServerKeyConfigurationException(previous: $e);
}
Prevention
- Store the private server key and passphrase together in config and validate at boot (health check that setDecryptKey succeeds).
- Test the passphrase with the gpg CLI before deploying config changes.
- Never pass public keys to setDecryptKey.
- Rotate keys with a documented procedure updating fingerprint + passphrase together.
When it happens
Trigger: Calling setDecryptKey($armoredKey, $passphrase) where importKeyIntoKeyring succeeds but adddecryptkey throws: wrong passphrase, key without a secret/private part, or corrupted/unusable key material.
Common situations: Server key passphrase changed in config but not in passbolt; importing only the public half of a pair; passphrase containing special characters mishandled by env/config; key expired or revoked.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- The OpenPGP server key defined in the config cannot be used…
- Can not upgrade. Please upgrade to the latest 1.x version…
- Decryption failed.
- The anonymous user id should be a UUID
- The OpenPGP server key defined in the config cannot be used…
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/0f64e069ff55f4d0.
Report an issue: GitHub.
Appendix: source
Thrown at config/Migrations/20170830064410_V162InitialMigration.php:49
$databaseName = $options['database'] ?? $options['name'];
// Check if v1 tables are present
$tables = [
'authentication_tokens', 'comments', 'controller_logs', 'email_queue',
'favorites', 'file_storage', 'gpgkeys', 'groups', 'groups_users', 'permissions',
'permissions_types', 'profiles', 'resources', 'roles', 'schema_migrations',
'secrets', 'user_agents', 'users',
];
$tableCount = 0;
foreach ($tables as $table) {
$exists = $this->hasTable($table);
if ($exists) {
$tableCount++;
}
}
// If this is an upgrade from v1
if ($tableCount > 0 && $tableCount < sizeof($tables)) {
throw new Exception('Can not upgrade. Some tables are missing.');
}
// If this is an upgrade from v1
if ($tableCount > 0) {
// Check the latest 1.x migration is done
$latestMigrationName = 'Migration_1_6_1';
$schemaMigrationResult = $this->query("SELECT * FROM schema_migrations WHERE class='$latestMigrationName'");
$schemaMigrationRows = $schemaMigrationResult->fetchAll();
if (!count($schemaMigrationRows)) {
throw new Exception('Can not upgrade. Please upgrade to the latest 1.x version first and retry. See https://help.passbolt.com/hosting/update.');
}
}
// Reset the collation just in case
if ($this->getAdapter()->getAdapterType() !== "pgsql") {
$this->execute('ALTER DATABASE `' . $databaseName . '` COLLATE utf8mb4_unicode_ci');
}
View on GitHub (pinned to 31c1bbc10f)