# How to Configure TLS/HTTPS Termination in workerd

> Configure TLS HTTPS termination in workerd by declaring an https socket variant and providing a PEM-encoded keypair via tlsOptions for automatic decryption before your Worker code receives connections.

- Repository: [Cloudflare/workerd](https://github.com/cloudflare/workerd)
- Tags: how-to-guide
- Published: 2026-03-18

---

**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:

```capnp
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; `*:443` listens on all addresses using the standard HTTPS port.
- **`keypair`** – Required for server authentication; both `privateKey` and `certificateChain` accept PEM files embedded via Cap'n Proto’s `embed` feature.
- **`trustBrowserCas`** – When `true`, the runtime trusts the built-in browser CA store, which is recommended for public-facing services.
- **`minVersion`** and **`cipherList`** – Optionally enforce security policies (e.g., `.tls1Dot2` or 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:

```capnp
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 in `src/workerd/server/workerd.capnp`, this struct maps directly to `kj::TlsContext::Options` and holds your keypair, verification settings, and TLS version constraints.

- **`Server::listenOnSockets`** – Located in `src/workerd/server/server.c++`, this function iterates over socket definitions. When it encounters an `https` variant, it instantiates a `kj::TlsContext` using the provided `tlsOptions`.

- **`kj::TlsContext::wrapNetwork`** – The KJ library function (invoked around lines 38-43 in `server.c++`) that returns a `kj::Network` implementation. This wrapper automatically performs TLS handshakes on incoming connections using the configured certificates and cipher suites.

- **[`kj/compat/tls.h`](https://github.com/cloudflare/workerd/blob/main/kj/compat/tls.h)** – The underlying KJ library header included in `server.c++` and `workerd-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:

```bash
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:

```bash
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 `https` variant in `workerd.capnp` to enable TLS termination.
- **Supply certificates** via `tlsOptions.keypair` using PEM-encoded files embedded directly into the configuration.
- **Configure security policies** through `minVersion`, `cipherList`, and `trustBrowserCas` to match your compliance requirements.
- **Enforce mTLS** by setting `requireClientCerts` and listing trusted CAs in `trustedCertificates`.
- **Rely on defaults** for outbound TLS; the implicit internet service uses `useSystemTrustStore = true` for 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.