passbolt/passbolt_api · error · InvalidJwtKeyPairException

The JWT public key could not be written.

Error message

The JWT public key could not be written.

What it means

createKeyPair() writes the extracted public key PEM to the public key path with file_put_contents(). A false return triggers this error, wrapped in InvalidJwtKeyPairException. It means the public key file could not be created or overwritten at getPublicKeyPath().

Solutions

  1. Ensure config/jwt exists and is writable by the runtime user: chown -R www-data:www-data config/jwt
  2. Check the public key path is a regular writable file, not a directory (ls -la config/jwt)
  3. Verify disk space and mount writability (df -h; touch config/jwt/test)
  4. If the container FS is read-only, generate the keys at build time or mount config/jwt as a writable volume

Example fix

// before
$export = file_put_contents($publicKeyPath, $publicKey);
// after
if (!is_writable(dirname($publicKeyPath))) {
    chmod(dirname($publicKeyPath), 0770); // or chown to runtime user
}
$export = file_put_contents($publicKeyPath, $publicKey);
Defensive patterns

Strategy: validation

Validate before calling

if (!is_dir($dir)) { mkdir($dir, 0770, true); }
if (!is_writable($dir)) { throw new \RuntimeException("{$dir} not writable by " . get_current_user()); }

Try / catch

try { $service->createKeyPair(); } catch (InvalidJwtKeyPairException $e) { // chown/chmod config/jwt to the runtime user, then retry }

Prevention

When it happens

Trigger: file_put_contents($publicKeyPath, $publicKey) returns false because config/jwt/ is missing, not writable by the current process, the path exists as a directory, or the filesystem is full/read-only.

Common situations: Web-server user (www-data) differs from CLI user that created config/jwt; SELinux contexts; deploying to immutable containers where config/ is read-only at runtime.

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


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/3d37df147c78ee1b. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/AccessToken/JwtKeyPairService.php:86

        $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.");
            }
        } catch (Throwable $e) {
            throw new InvalidJwtKeyPairException($e->getMessage());
        }
    }

    /**
     * Validate the key pair validity as defined by the public and secret services.

View on GitHub (pinned to 31c1bbc10f)