passbolt/passbolt_api · error · InvalidJwtKeyPairException
The JWT private key could not be written.
Error message
The JWT private key could not be written.
What it means
After openssl_pkey_new succeeds, createKeyPair() exports the private key to the secret key path via openssl_pkey_export_to_file(). When that call returns false (export failure, usually a filesystem permission problem on the target directory), this error is thrown and rethrown as InvalidJwtKeyPairException.
Solutions
- Create the JWT directory and give the web user ownership: mkdir -p config/jwt && chown www-data:www-data config/jwt
- Check free disk space and that the path is on a writable mount (df -h; mount)
- Verify no passphrase/openssl config error: capture openssl error with openssl_error_string() right after the failure
- Re-run the key pair generation command as the user that owns config/jwt
Example fix
// before
$export = openssl_pkey_export_to_file($pk, $secretKeyPath); // dir missing
// after
if (!is_dir(dirname($secretKeyPath))) {
mkdir(dirname($secretKeyPath), 0770, true);
}
$export = openssl_pkey_export_to_file($pk, $secretKeyPath); Defensive patterns
Strategy: validation
Validate before calling
$dir = dirname($secretKeyPath);
if (!is_dir($dir) || !is_writable($dir)) { mkdir($dir, 0770, true); } Try / catch
try { $service->createKeyPair(); } catch (InvalidJwtKeyPairException $e) { // check is_writable(config/jwt) and current process user } Prevention
- Create config/jwt with correct ownership (www-data) at deploy time
- Never run key generation as root without chown afterwards
- Monitor disk space on the config volume
When it happens
Trigger: openssl_pkey_export_to_file($pk, $secretKeyPath) returns false — the directory at getSecretKeyPath() (config/jwt/) does not exist, is not writable by the current user (e.g. www-data vs CLI user mismatch), or an OpenSSL passphrase/config issue blocks export.
Common situations: config/jwt/ not created before running the command; running `passbolt create jwt_keys` as root then the web server cannot continue; read-only or immutable mount; SELinux/AppArmor blocking writes.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- The JWT public key could not be written.
- The JWT private key could not be created.
- The JWT public key could not be extracted.
- The JWT public key could not be extracted.
- The JWT public key could not be read or is not valid.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/0dfee4c227684672.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/AccessToken/JwtKeyPairService.php:78
return;
}
$config = [
'digest_alg' => JwtTokenCreateService::JWT_ALG,
'private_key_bits' => $this->getKeyLength(),
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
$secretKeyPath = $this->getSecretKeyPath();
$publicKeyPath = $this->getPublicKeyPath();
try {
$pk = openssl_pkey_new($config);
if ($pk === false) {
throw new Exception('The JWT private key could not be created.');
}
$export = openssl_pkey_export_to_file($pk, $secretKeyPath);
if ($export === false) {
throw new Exception('The JWT private key could not be written.');
}
$publicKey = openssl_pkey_get_details($pk)['key'] ?? false;
if ($publicKey === false) {
throw new Exception('The JWT public key could not be extracted.');
}
$export = file_put_contents($publicKeyPath, $publicKey);
if ($export === false) {
throw new Exception('The JWT public key could not be written.');
}
$permission = 0640;
$res = chmod($secretKeyPath, $permission);
if (!$res) {
throw new Exception("The permission of $secretKeyPath could not be set to $permission.");
}
$res = chmod($publicKeyPath, $permission);
if (!$res) {
throw new Exception("The permission of $publicKeyPath could not be set to $permission.");View on GitHub (pinned to 31c1bbc10f)