denoland/deno · error · TypeError

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

Error message

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

What it means

The mirror of the cert-without-key check: loadTlsKeyPair throws this TypeError when key is specified but cert is undefined, again naming the API (Deno.connectTls or Deno.listenTls) in the message. The pair requirement is symmetric because a private key alone is not a usable TLS identity.

Solutions

  1. Supply the matching certificate string alongside the key
  2. Fail fast at startup: if (!cert || !key) throw new Error('both TLS_CERT and TLS_KEY are required')
  3. For a plain client TLS connection verified only against the server, omit both cert and key

Example fix

// before
Deno.listenTls({
  port: 443,
  key: Deno.env.get('TLS_KEY'),      // TLS_CERT not set
  cert: Deno.env.get('TLS_CERT'),
});

// after
const cert = Deno.env.get('TLS_CERT');
const key = Deno.env.get('TLS_KEY');
if (!cert || !key) throw new Error('TLS_CERT and TLS_KEY must both be set');
Deno.listenTls({ port: 443, cert, key });
Defensive patterns

Strategy: validation

Validate before calling

const cert = Deno.env.get('TLS_CERT');
const key = Deno.env.get('TLS_KEY');
if ((cert !== undefined) !== (key !== undefined)) {
  throw new Error('TLS_CERT and TLS_KEY must be set together');
}

Type guard

function hasCompleteKeyPair(o: { cert?: string; key?: string }): o is { cert: string; key: string } {
  return typeof o.cert === 'string' && typeof o.key === 'string' &&
    o.cert.length > 0 && o.key.length > 0;
}

Try / catch

try {
  Deno.listenTls(opts);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('`cert` must be specified')) {
    throw new Error('missing TLS_CERT (TLS_KEY was set but no certificate found)');
  }
  throw e;
}

Prevention

When it happens

Trigger: Deno.listenTls({ port: 443, key }) with no cert; Deno.connectTls({ hostname, port, key: clientKeyPem }) intending mTLS but forgetting the client certificate; env-driven config where the CERT variable is unset while KEY is set.

Common situations: Deployment pipelines that inject TLS_KEY but fail to inject TLS_CERT; template configs with one of the two lines deleted; client-certificate setups assembled by hand.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/9e13741383ed0e5a. Report an issue: GitHub.

Appendix: source

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

  // 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,
  hostname = "0.0.0.0",
  transport = "tcp",
  alpnProtocols = undefined,
  reusePort = false,
  tcpBacklog = 511,

View on GitHub (pinned to 9ad36f7a2c)