denoland/deno · error · TypeError

A key and certificate are required for `Deno.listenTls`

Error message

A key and certificate are required for `Deno.listenTls`

What it means

Deno.listenTls throws this TypeError when the options contain no key-pair material at all (hasTlsKeyPairOptions is false and no key resolver was registered). A TLS server must present a certificate, unlike Deno.connectTls where cert/key are optional client credentials. The check runs before the listening socket is created.

Solutions

  1. Generate or obtain a PEM cert/key pair and pass both strings
  2. For local dev create a self-signed pair: openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
  3. Load the files with await Deno.readTextFile before calling listenTls, and verify they are non-empty strings

Example fix

// before
const listener = Deno.listenTls({ port: 443 }); // no cert/key

// after
const cert = await Deno.readTextFile('cert.pem');
const key = await Deno.readTextFile('key.pem');
const listener = Deno.listenTls({ port: 443, cert, key });
Defensive patterns

Strategy: validation

Validate before calling

const cert = await Deno.readTextFile('cert.pem').catch(() => undefined);
const key = await Deno.readTextFile('key.pem').catch(() => undefined);
if (!cert?.includes('BEGIN') || !key?.includes('BEGIN')) {
  throw new Error('listenTls needs PEM cert and key; generate with openssl req -x509 -newkey rsa:2048');
}

Type guard

function hasTlsKeyPairOptions(o: Record<string, unknown>): boolean {
  return typeof (o as { cert?: unknown }).cert === 'string' &&
    typeof (o as { key?: unknown }).key === 'string';
}

Try / catch

try {
  Deno.listenTls({ port, cert, key });
} catch (e) {
  if (e instanceof TypeError && e.message.includes('key and certificate are required')) {
    throw new Error(`TLS cert/key missing or empty (cert=${!!cert}, key=${!!key})`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Deno.listenTls({ port: 443 }); Deno.listenTls({ port: 443, keyFormat: 'pem' }) (a format alone is not a key pair); cert/key read asynchronously and passed as undefined; Node-style option names like pfx/passphrase that the API does not read.

Common situations: Assuming listenTls works like connectTls with no certificate; pointing at Let's Encrypt files with wrong paths so readTextFile never ran; local dev servers started before certificates were generated.

Understand the failure class

Related errors


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

Appendix: source

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

    return op_tls_key_null();
  }
}

function listenTls({
  port = 0,
  hostname = "0.0.0.0",
  transport = "tcp",
  alpnProtocols = undefined,
  reusePort = false,
  tcpBacklog = 511,
}) {
  if (transport !== "tcp") {
    throw new TypeError(`Unsupported transport: '${transport}'`);
  }
  port = validatePort(port, true);

  if (!hasTlsKeyPairOptions(arguments[0])) {
    throw new TypeError(
      "A key and certificate are required for `Deno.listenTls`",
    );
  }
  const keyPair = loadTlsKeyPair("Deno.listenTls", arguments[0]);
  const { 0: rid, 1: localAddr } = op_net_listen_tls(
    { hostname, port },
    { alpnProtocols, reusePort, tcpBacklog },
    keyPair,
  );
  localAddr.transport = transport;
  return new TlsListener(rid, localAddr);
}

// deno-lint-ignore require-await
async function startTls(
  conn,
  {
    hostname = "127.0.0.1",

View on GitHub (pinned to 9ad36f7a2c)