How to Configure TLS/HTTPS Termination in workerd
To terminate TLS in workerd, declare an https socket variant in your Cap'n Proto configuration, provide a PEM-encoded keypair via tlsOptions, and the runtime will automatically instantiate a kj::TlsContext that handles decryption before connections reach your Worker code.
The cloudflare/workerd runtime manages HTTPS termination declaratively through its Cap'n Proto configuration schema rather than imperative application logic. When you define tlsOptions within an HTTPS socket declaration, the runtime—specifically in src/workerd/server/server.c++—constructs a KJ TLS context that performs handshakes using OpenSSL or BoringSSL, presenting your Worker with plain HTTP streams while managing encryption at the socket boundary.
How workerd Handles TLS Termination
TLS termination occurs at the socket level using the https variant of the Socket union defined in src/workerd/server/workerd.capnp (around lines 40-46). The configuration accepts a tlsOptions block that directly mirrors kj::TlsContext::Options, allowing you to specify certificate keypairs, trusted CAs, and cipher settings.
When the server starts, the Server::listenOnSockets method (in src/workerd/server/server.c++, around lines 28-33) parses each socket definition. For HTTPS sockets, it constructs a kj::TlsContext from your tlsOptions and wraps the underlying network using tls->wrapNetwork(*publicNetwork) (around lines 38-43). This wrapping intercepts every inbound TCP connection, performs the TLS handshake using your configured certificates, and presents the decrypted stream to the service implementation—completely abstracting TLS away from your Worker code.
Configuring an HTTPS Socket in Cap'n Proto
Basic TLS Setup with PEM Keypairs
Define an HTTPS socket in your workerd.capnp configuration file by selecting the https variant and populating tlsOptions with your server certificate and private key:
using Workerd = import "/workerd/workerd.capnp";
const config :Workerd.Config = (
sockets = [
( name = "https",
address = "*:443",
https = (
options = (
# Optional HttpOptions (timeouts, etc.)
),
tlsOptions = (
keypair = (
privateKey = embed "certs/key.pem",
certificateChain = embed "certs/cert.pem"
),
trustBrowserCas = true,
minVersion = .tls1Dot2
)
),
service = (name = "my-worker")
)
],
services = [
(name = "my-worker", worker = (modules = [(name = "main", esModule = embed "worker.js")]))
]
);
address– Specifies the bind interface;*:443listens on all addresses using the standard HTTPS port.keypair– Required for server authentication; bothprivateKeyandcertificateChainaccept PEM files embedded via Cap'n Proto’sembedfeature.trustBrowserCas– Whentrue, the runtime trusts the built-in browser CA store, which is recommended for public-facing services.minVersionandcipherList– Optionally enforce security policies (e.g.,.tls1Dot2or specific cipher strings like"TLS_AES_256_GCM_SHA384").
Enforcing Mutual TLS (mTLS)
To require client certificates for authentication, add requireClientCerts and specify trusted certificate authorities:
tlsOptions = (
keypair = (
privateKey = embed "certs/server-key.pem",
certificateChain = embed "certs/server-cert.pem"
),
requireClientCerts = true,
trustedCertificates = [embed "certs/client-ca.pem"],
minVersion = .tls1Dot3
)
Setting requireClientCerts to true forces the TLS handshake to reject any connection not presenting a valid certificate signed by one of the CAs listed in trustedCertificates.
Outbound TLS and System Trust Stores
If your Worker only makes outbound HTTPS requests (e.g., using fetch) and does not need to terminate inbound TLS, you can omit tlsOptions entirely. In this case, workerd relies on the implicit "internet" service created by Server::makeDefaultInternetService (in src/workerd/server/server.c++, around lines 68-71), which configures a kj::TlsContext with useSystemTrustStore = true. This default context trusts the system's root CA store for validating remote server certificates during outbound connections.
Internal Architecture of TLS Termination
The path from configuration to encrypted connection involves several key components:
-
workerd.capnp::TlsOptions– Defined around line 56 insrc/workerd/server/workerd.capnp, this struct maps directly tokj::TlsContext::Optionsand holds your keypair, verification settings, and TLS version constraints. -
Server::listenOnSockets– Located insrc/workerd/server/server.c++, this function iterates over socket definitions. When it encounters anhttpsvariant, it instantiates akj::TlsContextusing the providedtlsOptions. -
kj::TlsContext::wrapNetwork– The KJ library function (invoked around lines 38-43 inserver.c++) that returns akj::Networkimplementation. This wrapper automatically performs TLS handshakes on incoming connections using the configured certificates and cipher suites. -
kj/compat/tls.h– The underlying KJ library header included inserver.c++andworkerd-api.c++that provides the actual TLS implementation backed by OpenSSL or BoringSSL.
When a client connects to your HTTPS socket, the kj::ConnectionReceiver accepts the raw TCP connection, passes it through the TLS wrapper for decryption, and hands the resulting plaintext stream to your Worker service as a standard HTTP request.
Testing Your TLS Configuration
Save your configuration as my-config.capnp and start the server:
workerd serve my-config.capnp config
The serve command reads the Cap'n Proto config, binds to port 443 (or your specified address), and initializes the TLS context with your embedded certificates. Verify the setup using:
curl -v https://localhost:443/
The verbose output should show the TLS handshake completing with the certificate chain you provided in tlsOptions, followed by a successful HTTP response from your Worker.
Summary
- Declare HTTPS sockets using the
httpsvariant inworkerd.capnpto enable TLS termination. - Supply certificates via
tlsOptions.keypairusing PEM-encoded files embedded directly into the configuration. - Configure security policies through
minVersion,cipherList, andtrustBrowserCasto match your compliance requirements. - Enforce mTLS by setting
requireClientCertsand listing trusted CAs intrustedCertificates. - Rely on defaults for outbound TLS; the implicit internet service uses
useSystemTrustStore = truefor standard CA validation.
Frequently Asked Questions
Does workerd support automatic certificate provisioning like Let's Encrypt?
No, workerd does not automatically provision certificates. You must provide PEM-encoded private keys and certificate chains via the embed directive in your Cap'n Proto configuration. For production deployments, you typically handle certificate renewal outside of workerd and reload the configuration when certificates are updated.
Can I configure TLS cipher suites and protocol versions?
Yes. The tlsOptions block accepts minVersion (e.g., .tls1Dot2, .tls1Dot3) and cipherList (a colon-separated string of OpenSSL cipher names) as defined in the TlsOptions struct in src/workerd/server/workerd.capnp. These map directly to the underlying kj::TlsContext::Options used by the KJ library.
How do I handle TLS for outbound fetch requests in my Worker?
Outbound TLS is handled automatically by the default "internet" service unless you specify a custom tlsOptions. As implemented in Server::makeDefaultInternetService (src/workerd/server/server.c++), the default context uses useSystemTrustStore = true, trusting the system's root CA store to validate remote certificates. You do not need to configure anything for standard outbound HTTPS requests.
What happens if I omit tlsOptions in an HTTPS socket declaration?
The configuration will fail to validate or the socket will not function correctly for TLS. The https socket variant requires a valid tlsOptions block containing at least a keypair for server certificate presentation. Omitting this results in a runtime error during Server::listenOnSockets initialization.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →