n8n-io/n8n · error · Error
The Private Key field must contain a PEM private key (-----B
Error message
The Private Key field must contain a PEM private key (-----BEGIN PRIVATE KEY-----).
What it means
Thrown by buildClientAssertion when createPrivateKey(formatPemBlock(privateKey)) raises. The wrapper message states the Private Key field must be a PEM private key; the original crypto error is attached as `cause`. The key is used to RS256-sign the client-assertion JWT.
Source
Thrown at packages/@n8n/utils/src/client-assertion.ts:61
export function buildClientAssertion(options: BuildClientAssertionOptions): string {
const now = Math.floor(Date.now() / 1000);
const header = { alg: 'RS256', typ: 'JWT', x5t: certificateThumbprint(options.certificate) };
const payload = {
aud: options.accessTokenUri,
iss: options.clientId,
sub: options.clientId,
jti: randomUUID(),
iat: now,
nbf: now,
exp: now + ASSERTION_TTL_SECONDS,
};
let privateKey: KeyObject;
try {
privateKey = createPrivateKey(formatPemBlock(options.privateKey));
} catch (error) {
throw new Error(
'The Private Key field must contain a PEM private key (-----BEGIN PRIVATE KEY-----).',
{ cause: error },
);
}
// `createSign('RSA-SHA256')` also signs EC/Ed25519 keys, producing a signature
// that contradicts the pinned `alg: RS256` header. Reject non-RSA keys up front.
if (privateKey.asymmetricKeyType !== 'rsa') {
throw new Error('Certificate authentication requires an RSA private key');
}
const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(payload))}`;
const signature = createSign('RSA-SHA256').update(signingInput).sign(privateKey);
return `${signingInput}.${base64url(signature)}`;
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Confirm the PEM starts with '-----BEGIN PRIVATE KEY-----' (or 'BEGIN RSA PRIVATE KEY') and has the matching END.
- Make sure you pasted the private key, not the certificate.
- If the key is encrypted, decrypt it or supply the passphrase and use the appropriate loader.
- Inspect err.cause for the specific crypto error (e.g. unsupported key type).
Example fix
// before
buildClientAssertion({ ..., privateKey: certPem }); // swapped
// after
buildClientAssertion({ ..., privateKey: privateKeyPem }); // -----BEGIN PRIVATE KEY-----... Defensive patterns
Strategy: validation
Validate before calling
import { createPrivateKey } from 'node:crypto';
function assertValidPrivateKeyPem(key: string): void {
if (!key || !/-----BEGIN (RSA |EC |ENCRYPTED |)PRIVATE KEY-----/.test(key)) {
throw new Error('privateKey must be a PEM string bounded by BEGIN/END PRIVATE KEY markers');
}
createPrivateKey(key); // throws on malformed input
} Type guard
function looksLikePemPrivateKey(v: unknown): v is string {
return typeof v === 'string'
&& /-----BEGIN (RSA |EC |ENCRYPTED |OPENSSH |)PRIVATE KEY-----[\s\S]*-----END/.test(v);
} Try / catch
try {
return buildClientAssertion(opts);
} catch (e) {
if (/must contain a PEM private key/i.test(e?.message ?? '')) {
throw new ConfigError('private key field is not a valid PEM private key', { cause: e });
}
throw e;
} Prevention
- Validate the PEM markers before calling buildClientAssertion.
- Do not confuse the certificate and private key fields in config.
- For encrypted keys, decrypt or supply a passphrase via the appropriate loader.
When it happens
Trigger: Passing an empty key, a certificate PEM instead of a private key PEM, an encrypted PKCS#8 key without a passphrase, a malformed/truncated PEM, or a key in a format Node cannot parse (e.g. OpenSSH new-format).
Common situations: Swapping the certificate and private key fields in config; copy-paste losing the END marker; exporting a public key by mistake; encrypted keys where the runtime has no passphrase; line-ending corruption (CRLF) breaking PEM parsing.
Related errors
- The Certificate field must contain a PEM certificate (-----B
- Certificate authentication requires an RSA private key
- Unknown algorithm ${algorithm}. Available algorithms are: ${
- Cannot decrease maxIterations when resuming a run. Expected
- Checkpoint for runId ${this.runId} has pending tool calls —
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/2e4133e7f1229290.
Report an issue: GitHub.