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
- Supply the matching certificate string alongside the key
- Fail fast at startup: if (!cert || !key) throw new Error('both TLS_CERT and TLS_KEY are required')
- 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
- Fail fast at startup when exactly one of cert/key is configured
- Keep cert/key as an atomic pair in config structures, never as independent optional fields
- Smoke-test TLS config with a tiny script that calls listenTls before the real server boots
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
- If `cert` is specified, `key` must be specified as well for
- A key and certificate are required for `Deno.listenTls`
- If "keyFormat" is specified, it must be "pem": received
- Unsupported transport
- Dispatcher connect.ca must be a string, Buffer, or…
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)