denoland/deno · error · TypeError

Both 'cert' and 'key' must be provided to enable HTTPS

Error message

Both 'cert' and 'key' must be provided to enable HTTPS

What it means

When serve options request HTTPS (signaled by cert/key presence), serveInner requires both halves of the keypair: a certificate alone or a private key alone cannot build a TLS listener. This TypeError fires synchronously at startup when exactly one of `options.cert` / `options.key` is set, before any socket is bound.

Solutions

  1. Provide both `cert` and `key` PEM strings whenever either is present
  2. Validate before starting: `if (env.CERT || env.KEY) require both` and fail with a clear message
  3. Gate the whole TLS block on availability of both secrets, or default to plain HTTP in dev

Example fix

// before
Deno.serve({ port: 443, cert: pem, handler }); // key missing

// after
Deno.serve({
  port: 443,
  cert: await Deno.readText('./cert.pem'),
  key: await Deno.readText('./key.pem'),
  handler,
});
Defensive patterns

Strategy: validation

Validate before calling

// Require the keypair as a unit before starting:
const cert = env.TLS_CERT, key = env.TLS_KEY;
if ((cert ?? key) !== undefined && (!cert || !key)) {
  throw new Error('HTTPS requires both TLS_CERT and TLS_KEY');
}
const opts = cert && key ? { cert, key, handler } : { handler };
Deno.serve(opts);

Prevention

When it happens

Trigger: `Deno.serve({ cert })` without key, `key` present but `cert` missing, or conditionally spreading TLS options (`...(tls ?? {})`) where only one field is defined — e.g. cert loaded from an env var whose key counterpart is unset.

Common situations: Environment-driven TLS config where one secret is missing in a deploy environment, copy-paste from examples that only showed one field, templating bugs that drop one PEM.

Related errors


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

Appendix: source

Thrown at ext/http/00_serve.ts:1405

    loadBalanced: options[kLoadBalanced] ?? false,
    tcpBacklog: options.tcpBacklog,
  };

  if (options.certFile || options.keyFile) {
    throw new TypeError(
      "Unsupported 'certFile' / 'keyFile' options provided: use 'cert' / 'key' instead.",
    );
  }
  if (options.alpnProtocols) {
    throw new TypeError(
      "Unsupported 'alpnProtocols' option provided. 'h2' and 'http/1.1' are automatically supported.",
    );
  }

  let listener;
  if (wantsHttps) {
    if (!options.cert || !options.key) {
      throw new TypeError(
        "Both 'cert' and 'key' must be provided to enable HTTPS",
      );
    }
    listenOpts.cert = options.cert;
    listenOpts.key = options.key;
    listenOpts.alpnProtocols = ["h2", "http/1.1"];
    listener = listenTls(listenOpts);
    listenOpts.port = listener.addr.port;
  } else {
    listener = listen(listenOpts);
    listenOpts.port = listener.addr.port;
  }

  const addr = listener.addr;

  const onListen = (scheme) => {
    if (options.onListen) {
      options.onListen(addr);

View on GitHub (pinned to abd22074e4)