Summary
The Cloudflare Workers documentation for node:tls shows connect(url, { key: env.KEY, cert: env.CERT }) as a way to create a TLS connection with client key material:
https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/
However, with workerd 1.20260815.1 and on the production edge, both key / cert passed directly to tls.connect() and through createSecureContext({ key, cert }) are ignored. Even deliberately malformed PEM is accepted by the Workers runtime instead of failing during context creation.
This prevents a multi-tenant Worker from loading a tenant-specific client certificate from R2 or another secret store and presenting it for one outbound mTLS request.
Documentation inconsistency
The node:tls page documents key, cert, and createSecureContext():
https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/
But the node:https page says that key, cert, and pfx are unsupported and recommends a static mTLS binding:
https://developers.cloudflare.com/workers/runtime-apis/nodejs/https/
Minimal behavioral check
In native Node.js, this fails immediately as expected:
import { createSecureContext } from "node:tls";
createSecureContext({
key: "-----BEGIN PRIVATE KEY-----\nINVALID\n-----END PRIVATE KEY-----",
cert: "-----BEGIN CERTIFICATE-----\nINVALID\n-----END CERTIFICATE-----",
});
In the Workers runtime, the same invalid material does not fail and produces an empty SecureContext.
I also ran a sequential A/B test against a public mTLS endpoint using the same raw HTTP/1.1 transport and parser.
Native Node.js:
- valid client certificate passed to
connect(): HTTP 404 with the expected application error;
- the same certificate through
createSecureContext(): same HTTP 404;
- no client certificate: HTTP 403;
- malformed PEM: ASN.1 error before the request was written.
Remote Workers edge:
- valid
key / cert;
- valid material through
createSecureContext();
- no client certificate;
- malformed PEM;
All four Workers cases were indistinguishable: the request was written, the peer closed the connection, and zero response bytes were received. A public non-mTLS control using the same node:tls transport and HTTP parser returned HTTP 301, so the generic socket/parser path was working.
No private key, certificate, tenant identifier, or production payload is included in this report.
Current implementation
The source for the installed workerd version appears to confirm the behavior:
Use case
We operate a multi-tenant invoicing SaaS. Each tenant uploads its own PKCS#12/PFX A1 client certificate, stored encrypted in R2. The application can load and convert the selected PFX to PEM in memory, but it cannot present that PEM in the outbound mTLS handshake.
A static mTLS binding per tenant would require control-plane provisioning and Worker reconfiguration for every onboarding, renewal, and revocation. We need either:
- the documented
tls.connect({ key, cert }) behavior to be implemented; or
- another supported per-request API that accepts in-memory client certificate material.
If dynamic client certificates are intentionally unsupported, the node:tls documentation should state this explicitly and avoid the current key / cert example.
Environment
- Wrangler:
4.124.0
- Bundled workerd:
1.20260815.1
- Compatibility date:
2026-08-22
- Compatibility flag:
nodejs_compat
- Native control: Node.js
v24.13.0
- Reproduced with
wrangler dev --remote on the Cloudflare edge
Summary
The Cloudflare Workers documentation for
node:tlsshowsconnect(url, { key: env.KEY, cert: env.CERT })as a way to create a TLS connection with client key material:https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/
However, with workerd
1.20260815.1and on the production edge, bothkey/certpassed directly totls.connect()and throughcreateSecureContext({ key, cert })are ignored. Even deliberately malformed PEM is accepted by the Workers runtime instead of failing during context creation.This prevents a multi-tenant Worker from loading a tenant-specific client certificate from R2 or another secret store and presenting it for one outbound mTLS request.
Documentation inconsistency
The
node:tlspage documentskey,cert, andcreateSecureContext():https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/
But the
node:httpspage says thatkey,cert, andpfxare unsupported and recommends a static mTLS binding:https://developers.cloudflare.com/workers/runtime-apis/nodejs/https/
Minimal behavioral check
In native Node.js, this fails immediately as expected:
In the Workers runtime, the same invalid material does not fail and produces an empty
SecureContext.I also ran a sequential A/B test against a public mTLS endpoint using the same raw HTTP/1.1 transport and parser.
Native Node.js:
connect(): HTTP 404 with the expected application error;createSecureContext(): same HTTP 404;Remote Workers edge:
key/cert;createSecureContext();All four Workers cases were indistinguishable: the request was written, the peer closed the connection, and zero response bytes were received. A public non-mTLS control using the same
node:tlstransport and HTTP parser returned HTTP 301, so the generic socket/parser path was working.No private key, certificate, tenant identifier, or production payload is included in this report.
Current implementation
The source for the installed workerd version appears to confirm the behavior:
createSecureContext()does not create a customizable TLS context and setscontext = undefined:https://github.com/cloudflare/workerd/blob/v1.20260815.1/src/node/internal/internal_tls_common.ts#L38-L82
connect()does not forwardoptions.keyoroptions.cert:https://github.com/cloudflare/workerd/blob/v1.20260815.1/src/node/internal/internal_tls_wrap.ts#L638-L720
expectedServerHostnametostartTls():https://github.com/cloudflare/workerd/blob/v1.20260815.1/src/node/internal/internal_tls_wrap.ts#L438-L509
setKeyCert()explicitly reports that changing key/certificate material is unsupported:https://github.com/cloudflare/workerd/blob/v1.20260815.1/src/node/internal/internal_tls_wrap.ts#L552-L558
Use case
We operate a multi-tenant invoicing SaaS. Each tenant uploads its own PKCS#12/PFX A1 client certificate, stored encrypted in R2. The application can load and convert the selected PFX to PEM in memory, but it cannot present that PEM in the outbound mTLS handshake.
A static mTLS binding per tenant would require control-plane provisioning and Worker reconfiguration for every onboarding, renewal, and revocation. We need either:
tls.connect({ key, cert })behavior to be implemented; orIf dynamic client certificates are intentionally unsupported, the
node:tlsdocumentation should state this explicitly and avoid the currentkey/certexample.Environment
4.124.01.20260815.12026-08-22nodejs_compatv24.13.0wrangler dev --remoteon the Cloudflare edge