denoland/deno · error · TypeError

If `cert` is specified, `key` must be specified as well for

Error message

If `cert` is specified, `key` must be specified as well for `${api}`

What it means

loadTlsKeyPair() requires cert and key to be supplied together: specifying cert without key throws this TypeError, naming the API ('Deno.connectTls' or 'Deno.listenTls'). A certificate alone cannot complete TLS authentication; its matching private key is mandatory.

Source

Thrown at ext/net/02_tls.js:148

function loadTlsKeyPair(api, {
  keyFormat,
  cert,
  key,
}) {
  // TODO(mmastrac): remove this temporary symbol when the API lands
  if (arguments[1][resolverSymbol] !== undefined) {
    return createTlsKeyResolver(arguments[1][resolverSymbol]);
  }

  // Check for "pem" format
  if (keyFormat !== undefined && keyFormat !== "pem") {
    throw new TypeError(
      `If "keyFormat" is specified, it must be "pem": received "${keyFormat}"`,
    );
  }

  if (cert !== undefined && key === undefined) {
    throw new TypeError(
      `If \`cert\` is specified, \`key\` must be specified as well for \`${api}\``,
    );
  }
  if (cert === undefined && key !== undefined) {
    throw new TypeError(
      `If \`key\` is specified, \`cert\` must be specified as well for \`${api}\``,
    );
  }

  if (cert !== undefined) {
    return op_tls_key_static(cert, key);
  } else {
    return op_tls_key_null();
  }
}

function listenTls({
  port = 0,

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Provide both cert and key as PEM strings
  2. Load them symmetrically (same env prefix / directory) and fail fast at startup if either is missing
  3. Double-check secret names and that the key is not gated behind a separate conditional

Example fix

// before
Deno.listenTls({ port: 443, cert: Deno.readTextFileSync("cert.pem") });

// after
Deno.listenTls({
  port: 443,
  cert: Deno.readTextFileSync("cert.pem"),
  key: Deno.readTextFileSync("key.pem"),
});
Defensive patterns

Strategy: validation

Validate before calling

function requireKeyPair(opts: { cert?: string; key?: string }): { cert: string; key: string } {
  const { cert, key } = opts;
  if ((cert !== undefined || key !== undefined) && (cert === undefined || key === undefined)) {
    throw new Error("TLS options require both cert and key together");
  }
  return { cert: cert!, key: key! };
}

Type guard

function hasCompleteKeyPair(o: { cert?: string; key?: string }): o is { cert: string; key: string } {
  return (o.cert === undefined) === (o.key === undefined);
}

Prevention

When it happens

Trigger: Deno.listenTls({ port: 443, cert }) with no key; Deno.connectTls({ cert }) for mTLS without the client key; option objects built conditionally that only set cert.

Common situations: TLS_CERT env var set but TLS_KEY empty or misspelled; deployment secrets mounted asymmetrically; spreads that drop one field ({ ...certOnly }).

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/7c0bdf2360b383c4. Report an issue: GitHub.