Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
77dd17c
net, tls: consolidate the open node:net, node:tls, Bun.connect, Bun.l…
Jarred-Sumner Oct 22, 2026
a970423
Fix the Windows and macOS test failures, the mordant findings and wha…
Jarred-Sumner Oct 22, 2026
49545fd
http: consolidate the open fetch, Bun.serve, node:http, node:http2 an…
Jarred-Sumner Oct 22, 2026
feebbe0
Fix what review and a differential fuzzer found, the Windows test and…
Jarred-Sumner Oct 22, 2026
8758dd7
Fix what review, a differential fuzzer and the first CI run found
Jarred-Sumner Oct 22, 2026
6d5b579
Fix what the second CI run and review found
Jarred-Sumner Oct 22, 2026
01395ab
Fix what review found in the third push, and the two Windows test rows
Jarred-Sumner Oct 22, 2026
9028a27
node:http2: keep a peer's GOAWAY from crashing a client through a pus…
Jarred-Sumner Oct 22, 2026
c263d3f
node:http: send the head of a HEAD, 204 or 304 response at end() agai…
Jarred-Sumner Oct 22, 2026
76bc84e
node:http, node:http2: fixes from review of the last push
Jarred-Sumner Oct 22, 2026
826df79
fetch, node:http2: take back the per-request cost the release benchma…
Jarred-Sumner Oct 22, 2026
69453d7
Fix the last three differences the differential fuzzer found, and use…
Jarred-Sumner Oct 22, 2026
c2a1d14
fetch: keep FetchTasklet's mutex and atomic flags in one cache line a…
Jarred-Sumner Oct 22, 2026
9337309
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 22, 2026
67254fe
Merge branch 'main' into claude/http-consolidated
Jarred-Sumner Oct 22, 2026
41b5bfa
Refuse a descriptor this process already uses, and move rules to the …
Jarred-Sumner Oct 22, 2026
9b433a0
Read each TLS server option the way Node does, and move rules to the …
Jarred-Sumner Oct 23, 2026
99da494
Read secure context options by name everywhere, fire socket timeouts …
Jarred-Sumner Oct 23, 2026
c5b696d
Merge branch 'main', give the TLS handshake deadline its own timer, a…
Jarred-Sumner Oct 23, 2026
ae5e404
Fix the lint, clippy and mordant findings, check the descriptor table…
Jarred-Sumner Oct 23, 2026
c7053e2
socket: pass the SSL_CTX to the connect instead of keeping it on the …
Jarred-Sumner Oct 23, 2026
dace09c
Keep the TLS half's queued tail when the raw half of an upgradeTLS() …
Jarred-Sumner Oct 23, 2026
77b8a1c
Merge branch 'main', and close without an error when a timeout handle…
Jarred-Sumner Oct 23, 2026
387f8cc
Keep an error that a TLS callback caught out of its handshake, and re…
Jarred-Sumner Oct 23, 2026
865db48
tls: let the SSL_CTX cache keep the 16 contexts asked for last, so th…
Jarred-Sumner Oct 23, 2026
0ed6d03
test(tls): wait for a Worker's 'close' event before counting the cont…
Jarred-Sumner Oct 23, 2026
851a8c3
socket: close a paused socket whose queued write failed, and report a…
Jarred-Sumner Oct 23, 2026
ebb4da0
Give each HTTP rule one owner: one node:http server per kind of conne…
Jarred-Sumner Oct 23, 2026
a3ae79f
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 23, 2026
525fdb4
Fix the lint, clippy and mordant findings
Jarred-Sumner Oct 23, 2026
a919d6b
Fix what the first CI run on the second pass found, and delete more d…
Jarred-Sumner Oct 23, 2026
e02ba32
Address review comments, and bring in the net / TLS branch's newest s…
Jarred-Sumner Oct 23, 2026
d132a01
node:http: deliver a request body that is read after an await, end a …
Jarred-Sumner Oct 23, 2026
6e8e435
fetch, node:http, Bun.serve: fixes from the final round of verificati…
Jarred-Sumner Oct 23, 2026
5235341
node:http2, node:http, usockets: session.destroy() after close(), unc…
Jarred-Sumner Oct 23, 2026
cf707c7
node:http, node:cluster, child_process, fetch: fixes from the final r…
Jarred-Sumner Oct 23, 2026
eb8280d
node:http: bind the 'finish' callbacks again instead of closing over …
Jarred-Sumner Oct 23, 2026
baf0013
test(node:net, node:http): on Windows a send is never partial, and a …
Jarred-Sumner Oct 23, 2026
a021d40
node:http: honor listen({ ipv6Only: true })
Jarred-Sumner Oct 23, 2026
0676659
Merge branch 'main' into claude/http-consolidated, and make a client …
Jarred-Sumner Oct 24, 2026
c4f8ce9
node:http2: a server session reads as destroyed from the start of its…
Jarred-Sumner Oct 24, 2026
44e4858
node:stream, HTMLRewriter, node:http, node:http2, node:net: fixes fro…
Jarred-Sumner Oct 24, 2026
dee4a75
Merge branch 'main' into claude/http-consolidated
Jarred-Sumner Oct 24, 2026
be1b3a2
usockets, node:child_process: fixes from review; run the Bun.stdin.st…
Jarred-Sumner Oct 24, 2026
04af03e
Merge branch 'main' into claude/http-consolidated
Jarred-Sumner Oct 24, 2026
126551b
node:http2: fixes from review
Jarred-Sumner Oct 24, 2026
9d5ba06
node:http2: a stream that resumes while the session reads gets its WI…
Jarred-Sumner Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 5 additions & 8 deletions docs/bundler/executables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -800,12 +800,10 @@ import styles from "./styles.css" with { type: "file" };
import { file, serve } from "bun";

serve({
// Inside the compiled executable, file() on an embedded path returns an
// in-memory Blob, which routes only accepts wrapped in a Response.
routes: {
"/favicon.ico": new Response(file(favicon)),
"/logo.png": new Response(file(logo)),
"/styles.css": new Response(file(styles)),
"/favicon.ico": file(favicon),
"/logo.png": file(logo),
"/styles.css": file(styles),
},
fetch(req) {
return new Response("Not found", { status: 404 });
Expand Down Expand Up @@ -988,12 +986,11 @@ import "./public/styles.css" with { type: "file" };
import { embeddedFiles, serve } from "bun";

// Build static routes from all embedded files
const staticRoutes: Record<string, Response> = {};
const staticRoutes: Record<string, Blob> = {};
for (const blob of embeddedFiles) {
// Remove hash from filename: "icon-a1b2c3d4.png" -> "icon.png"
const name = blob.name.replace(/-[a-z0-9]+\./, ".");
// embeddedFiles are plain Blobs, which routes does not accept directly
staticRoutes[`/${name}`] = new Response(blob);
staticRoutes[`/${name}`] = blob;
}

serve({
Expand Down
10 changes: 5 additions & 5 deletions docs/guides/http/fetch-unix.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: fetch with unix domain sockets in Bun
sidebarTitle: Fetch with unix domain sockets
title: fetch with Unix domain sockets in Bun
sidebarTitle: Fetch with Unix domain sockets
mode: center
---

In Bun, `fetch()` can send HTTP requests over a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) with the `unix` option.
In Bun, `fetch()` can send HTTP requests over a [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) with the `unix` option.

```ts fetch-unix.ts icon="/icons/typescript.svg"
const unix = "/var/run/docker.sock";
Expand All @@ -17,9 +17,9 @@ console.log(body); // { ... }

---

The `unix` option is the local file path to a unix domain socket. `fetch()` sends the request over that socket instead of a TCP connection. HTTPS is also supported: use the `https://` protocol in the URL instead of `http://`.
The `unix` option is the local file path to a Unix domain socket. `fetch()` sends the request over that socket instead of a TCP connection. HTTPS is also supported: use the `https://` protocol in the URL instead of `http://`.

To send a `POST` request to an API endpoint over a unix domain socket:
To send a `POST` request to an API endpoint over a Unix domain socket:

```ts fetch-unix.ts icon="/icons/typescript.svg"
const response = await fetch("https://hostname/a/path", {
Expand Down
15 changes: 13 additions & 2 deletions docs/guides/http/stream-file.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,28 @@ new Response(Bun.file("./img.png")).headers.get("Content-Type");

Putting it all together with [`Bun.serve()`](/runtime/http/server).

The client chooses the request path, and `new URL(req.url).pathname` starts with `/`. Passing it straight to `Bun.file()` would serve any file the process can read. Resolve it against a fixed directory and reject anything outside that directory.

```ts server.ts icon="/icons/typescript.svg"
import path from "node:path";

const root = path.resolve("./public");

// static file server
Bun.serve({
async fetch(req) {
const path = new URL(req.url).pathname;
const file = Bun.file(path);
const filePath = path.join(root, decodeURIComponent(new URL(req.url).pathname));
if (!filePath.startsWith(root + path.sep)) return new Response("Forbidden", { status: 403 });

const file = Bun.file(filePath);
if (!(await file.exists())) return new Response("Not Found", { status: 404 });
return new Response(file);
},
});
```

To serve a whole directory without writing this yourself, use a [directory route](/runtime/http/routing#directory-routes).

---

See [`Bun.write()`](/runtime/file-io#writing-files-bun-write).
4 changes: 3 additions & 1 deletion docs/runtime/debugger.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ await fetch("https://example.com", {
```

```txt
[fetch] $ curl --http1.1 "https://example.com/" -X POST -H "content-type: application/json" -H "Connection: keep-alive" -H "User-Agent: Bun/1.3.3" -H "Accept: */*" -H "Host: example.com" -H "Accept-Encoding: gzip, deflate, br" --compressed -H "Content-Length: 13" --data-raw "{\"foo\":\"bar\"}"
[fetch] $ curl --http1.1 'https://example.com/' -X POST -H 'content-type: application/json' -H 'Connection: keep-alive' -H 'User-Agent: Bun/1.3.3' -H 'Accept: */*' -H 'Host: example.com' -H 'Accept-Encoding: gzip, deflate, br' --compressed -H 'Content-Length: 13' --data-raw '{"foo":"bar"}'
[fetch] > HTTP/1.1 POST https://example.com/
[fetch] > content-type: application/json
[fetch] > Connection: keep-alive
Expand All @@ -169,6 +169,8 @@ await fetch("https://example.com", {

The lines with `[fetch] >` are the request from your local code, and the lines with `[fetch] <` are the response from the remote server.

On macOS and Linux, each argument of the `curl` command is single-quoted, so a shell like `bash` or `zsh` passes `$`, backticks and double quotes to `curl` as they are. On Windows, the arguments are double-quoted for `cmd.exe`.

To print without the `curl` command, set `BUN_CONFIG_VERBOSE_FETCH` to `true`.

```ts index.ts icon="/icons/typescript.svg"
Expand Down
1 change: 1 addition & 0 deletions docs/runtime/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Bun reads these environment variables to configure aspects of its behavior.
| `NO_COLOR` | If `NO_COLOR=1`, then ANSI color output is [disabled](https://no-color.org/). |
| `FORCE_COLOR` | If `FORCE_COLOR=1`, then ANSI color output is forced on, even if `NO_COLOR` is set. |
| `BUN_CONFIG_MAX_HTTP_REQUESTS` | Sets the maximum number of concurrent HTTP requests sent by `fetch` and `bun install`. `fetch` defaults to `256`. `bun install` defaults to `64`, and its `--network-concurrency` flag overrides this variable. Lower it if you run into rate limits or connection issues. |
| `BUN_CONFIG_HTTP_IDLE_TIMEOUT` | Seconds an HTTP request sent by `fetch` or `bun install` can go without progress before it fails with a timeout error. The timer covers the TLS handshake, the response headers and the response body. Defaults to `300`. `0` disables it. |
| `BUN_INSTALL_WINDOWS_RENAME_RETRY_MS` | Windows only. How many milliseconds `bun install` keeps retrying a rename that fails because another process, usually antivirus, has a file in the directory open. Defaults to `5000`. |
| `BUN_CONFIG_WS_CLOSE_TIMEOUT` | Sets how many seconds a `WebSocket` client waits after sending a Close frame for the server to reply and close the connection. After that, Bun closes it. If the server never sent a Close frame, the `close` event has code `1006`. Defaults to `30`. `0` disables the timeout. |
| `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD` | If `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true`, then `bun --watch` does not clear the console on reload |
Expand Down
9 changes: 8 additions & 1 deletion docs/runtime/http/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ In development mode, when a request handler throws and no `error` handler return

To handle server-side errors, implement an `error` handler. Return a `Response` to serve to the client when an error occurs. In `development` mode, this response replaces Bun's default error page.

Error messages and stack traces can contain request input and absolute file paths. Escape them with [`Bun.escapeHTML()`](/runtime/utils#bun-escapehtml) before you put them in HTML, and don't send them to clients in production.

```ts
Bun.serve({
fetch(req) {
throw new Error("woops!");
},
error(error) {
return new Response(`<pre>${error}\n${error.stack}</pre>`, {
console.error(error);
if (process.env.NODE_ENV === "production") {
return new Response("Internal Server Error", { status: 500 });
}
return new Response(`<pre>${Bun.escapeHTML(`${error}\n${error.stack}`)}</pre>`, {
status: 500,
headers: {
"Content-Type": "text/html",
},
Expand Down
26 changes: 18 additions & 8 deletions docs/runtime/http/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ Bun.serve({
});
```

Bun automatically decodes percent-encoded route parameter values, including Unicode characters. Bun replaces invalid Unicode with the Unicode replacement character (`\uFFFD`).
A path segment is a parameter only when it starts with `:`, and the parameter captures the whole segment. A `:` anywhere else in a segment is matched literally, so `/files:batchGet/:id` has one parameter, `id`.

Bun automatically decodes percent-encoded route parameter values, including Unicode characters. Bun replaces invalid Unicode with the Unicode replacement character (`\uFFFD`). A `%` that isn't followed by two hexadecimal digits stays a literal `%`.

### Static responses

Expand Down Expand Up @@ -163,10 +165,14 @@ Bun caches static route responses for the lifetime of the server object. To relo
Serving a file from a route behaves differently depending on whether you buffer the file content or serve it directly:

```ts
const logo = Bun.file("./logo.png");

Bun.serve({
routes: {
// Static route - content is buffered in memory at startup
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
"/logo.png": new Response(await logo.bytes(), {
headers: { "Content-Type": logo.type },
}),

// File route - content is read from filesystem on each request
"/download.zip": new Response(Bun.file("./download.zip")),
Expand All @@ -181,6 +187,7 @@ Bun.serve({
- **If-None-Match** - Returns `304 Not Modified` when client ETag matches
- **No 404 handling** - Missing files cause startup errors, not runtime 404s
- **Memory usage** - Full file content stored in RAM
- **No Content-Type** - Bytes have no type, so set the `Content-Type` header yourself. `file.type` is the type Bun infers from the file extension
- **Best for**: Small static assets, API responses, frequently accessed files

**File routes** (`new Response(Bun.file(path))`) read from filesystem per request:
Expand All @@ -207,7 +214,9 @@ Bun.serve({
});
```

Bun percent-decodes the part of the request URL after the prefix once and opens it relative to `dir`. Bun rejects non-canonical paths with `404`, so the served path is always the path the router matched. A path is non-canonical if it contains `.`, `..`, empty segments, `%2F`, or a `%XX` sequence encoding a character that may appear literally in a path segment. On Linux the open uses `openat2(RESOLVE_IN_ROOT)`, so the kernel clamps symlinks that would escape `dir`.
Bun percent-decodes the part of the request URL after the prefix once and opens it relative to `dir`. Bun rejects non-canonical paths with `404`, so the served path is always the path the router matched. A path is non-canonical if it contains a segment that is exactly `.` or `..`, an empty segment, `%2F`, or a `%XX` sequence encoding a character that may appear literally in a path segment. On Linux the open uses `openat2(RESOLVE_IN_ROOT)`, so the kernel clamps symlinks that would escape `dir`. On macOS and Windows, and on Linux when `openat2` is unavailable (kernels before 5.6, or a seccomp policy that blocks it), Bun follows symlinks, so a symlink inside `dir` exposes its target even when the target is outside `dir`.

Bun doesn't filter dotfiles. It serves any regular file under `dir`, including a `.env` file or the contents of a `.git` directory. Point `dir` at a directory that only contains public files.

<Info>
Routing is case-sensitive, but filesystems on macOS and Windows are case-insensitive by default. As a result, a
Expand Down Expand Up @@ -252,15 +261,16 @@ Bun.serve({
fetch(req) {
// parse `Range` header
const [start = 0, end = Infinity] = req.headers
.get("Range") // Range: bytes=0-100
.split("=") // ["Range: bytes", "0-100"]
.get("Range") // "bytes=0-100"
.split("=") // ["bytes", "0-100"]
.at(-1) // "0-100"
.split("-") // ["0", "100"]
.map(Number); // [0, 100]
.split("-") // ["0", "100"], or ["100", ""] for "bytes=100-"
.map(part => (part ? Number(part) : undefined)); // [0, 100]

// return a slice of the file
// the end of a `Range` is inclusive and the end of a `slice` is exclusive, so add 1
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
return new Response(bigFile.slice(start, end + 1));
},
});
```
Expand Down
58 changes: 52 additions & 6 deletions docs/runtime/http/server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,48 @@ const server = Bun.serve({
console.log(`Server running at ${server.url}`);
```

### Splitting routes across files

`routes` is a plain object, so you can define groups of routes in separate files and spread them together. Annotate `req` with `BunRequest<path>` to keep `req.params` typed.

```ts title="users.ts" icon="/icons/typescript.svg"
import type { BunRequest } from "bun";

export const userRoutes = {
"/users/:id": (req: BunRequest<"/users/:id">) => {
return new Response(`Hello User ${req.params.id}!`);
},
};
```

```ts title="posts.ts" icon="/icons/typescript.svg"
export const postRoutes = {
"/api/posts": {
GET: () => new Response("List posts"),
POST: async (req: Request) => {
const post = await req.json();
return Response.json({ created: true, post });
},
},
};
```

```ts title="index.ts" icon="/icons/typescript.svg"
import { userRoutes } from "./users";
import { postRoutes } from "./posts";

const server = Bun.serve({
routes: {
...userRoutes,
...postRoutes,
},

fetch(req) {
return new Response("Not Found", { status: 404 });
},
});
```

---

## HTML imports
Expand Down Expand Up @@ -147,7 +189,7 @@ NODE_PORT=4002 bun server.ts

## Unix domain sockets

To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket), pass the `unix` option with the path to the socket.
To listen on a [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket), pass the `unix` option with the path to the socket.

```ts
Bun.serve({
Expand All @@ -171,7 +213,7 @@ Bun.serve({
});
```

Unlike unix domain sockets, abstract namespace sockets are not bound to the filesystem and are automatically removed when the last reference to the socket is closed.
Unlike Unix domain sockets, abstract namespace sockets are not bound to the filesystem and are automatically removed when the last reference to the socket is closed.

---

Expand Down Expand Up @@ -213,7 +255,7 @@ Bun.serve({
```

<Note>
`http3` is not supported with unix domain sockets: QUIC requires a UDP port. `http1: false` requires `http2: true` or
`http3` is not supported with Unix domain sockets: QUIC requires a UDP port. `http1: false` requires `http2: true` or
`http3: true`.
</Note>

Expand Down Expand Up @@ -252,7 +294,7 @@ Set `http1: false` to refuse HTTP/1.x clients. Over TLS, clients that offer ALPN

By default, `Bun.serve` closes connections after **10 seconds** of inactivity. A connection is idle when no data is being sent or received. That includes in-flight requests where your handler is still running but hasn't written any bytes to the response yet. Browsers and `fetch()` clients see this as a connection reset.

To configure this, set the `idleTimeout` field (in seconds). The maximum value is `255`, and `0` disables the timeout entirely.
To configure this, set the `idleTimeout` field (in seconds). The maximum value is `255`, and `0` disables the timeout entirely. Bun checks for idle connections every 4 seconds, so a connection is closed up to 4 seconds after its `idleTimeout` has passed, and never before.

```ts
Bun.serve({
Expand All @@ -265,6 +307,8 @@ Bun.serve({
});
```

`idleTimeout` is also how long Bun keeps an idle keep-alive connection open between requests. Behind a load balancer or a reverse proxy, set it above the keep-alive time the proxy uses for its upstream connections, or to `0`. Otherwise the proxy can send a request on a connection that Bun is closing.

<Note>
**Streaming & Server-Sent Events** — The idle timer applies while a response is being streamed. If your stream goes
quiet for longer than `idleTimeout`, Bun closes the connection mid-response. For long-lived streams, disable the
Expand Down Expand Up @@ -336,6 +380,8 @@ await server.stop(true);

By default, `stop()` allows in-flight requests and WebSocket connections to complete. The server closes idle keep-alive connections immediately. Connections with a request in flight close once the server has sent their response. Pass `true` to immediately terminate all connections instead. Open WebSockets close with code `1001`. The returned promise resolves once every connection has closed.

The last HTTP/1.1 response on a connection carries `Connection: close`. An HTTP/2 connection gets a `GOAWAY` frame, and once its streams are done the server ends its side of the connection. The connection closes when the client ends its side too, or after 10 seconds.

### `server.closeIdleConnections()`

To close keep-alive connections that are not currently serving a request, without stopping the server:
Expand Down Expand Up @@ -392,7 +438,7 @@ Use this for development and hot reloading. You can update only `fetch`, `error`

### `server.timeout(Request, seconds)`

Override the idle timeout for an individual request. Pass `0` to disable the timeout entirely for that request.
Override the idle timeout for an individual request. Pass `0` to disable the timeout entirely for that request. The maximum is `255`, and larger values are clamped to it.

```ts
const server = Bun.serve({
Expand Down Expand Up @@ -621,7 +667,7 @@ interface Server extends Disposable {
* Make a request to the running server.
* Useful for testing or internal routing.
*/
fetch(request: Request | string): Response | Promise<Response>;
fetch(request: Request | string | URL, init?: RequestInit): Response | Promise<Response>;

/**
* Upgrade an HTTP request to a WebSocket connection.
Expand Down
16 changes: 16 additions & 0 deletions docs/runtime/http/tls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ Bun.serve({
});
```

### Protocol versions

To limit the TLS protocol versions a connection can use, set `minVersion` and `maxVersion`. They take the same names as `node:tls`: `"TLSv1"`, `"TLSv1.1"`, `"TLSv1.2"` and `"TLSv1.3"`. The defaults are `"TLSv1.2"` and `"TLSv1.3"`.

```ts
Bun.serve({
tls: {
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
minVersion: "TLSv1.3", // [!code ++]
},
});
```

The same two options work in every API that takes a `tls` object, such as `fetch`, `Bun.connect` and `Bun.listen`. When `tls` is an array (see [SNI](#server-name-indication-sni) below), every entry uses the range of the first one.

### CA Certificates

Pass `ca` to override the trusted CA certificates. By default, the server trusts the list of well-known CAs curated by Mozilla; setting `ca` replaces that list.
Expand Down
Loading
Loading