Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 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
feebbe0
Fix what review and a differential fuzzer found, the Windows test and…
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
9337309
Merge branch 'main' into claude/net-tls-consolidated
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
5c5058d
sockets: end TLS connections in order when a thread or a listener goe…
Jarred-Sumner Oct 23, 2026
4c15330
windows: leave a socket that another process also holds alone at exit
Jarred-Sumner Oct 23, 2026
3b03862
usockets: only a thread that ends closes its TLS connections with a F…
Jarred-Sumner Oct 23, 2026
f12fe51
usockets: count the time a TLS socket waits in the handshake queue pe…
Jarred-Sumner Oct 23, 2026
f69ff9d
usockets: a socket that waits in the TLS handshake queue keeps the ti…
Jarred-Sumner Oct 23, 2026
7e565f7
test(usockets): the sweep timer fixture no longer depends on the orde…
Jarred-Sumner Oct 23, 2026
c012b4f
test(serve): the pre-handshake reap test no longer depends on how fas…
Jarred-Sumner Oct 23, 2026
9920c47
test(serve): the pre-handshake reap test reports a failure as soon as…
Jarred-Sumner Oct 23, 2026
e4ab1cf
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 24, 2026
57d2b5a
tls: the SSL_CTX cache keeps no context that read a file past its las…
Jarred-Sumner Oct 24, 2026
835a7da
node:net: a server whose keepAliveInitialDelay is past what the handl…
Jarred-Sumner Oct 24, 2026
d134134
usockets: a thread that ends sends no close_notify on its TLS connect…
Jarred-Sumner Oct 24, 2026
e818374
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 24, 2026
977f2e7
Bun.serve: fixes from review
Jarred-Sumner Oct 24, 2026
b5bfef3
Bun.serve: a loop tick run by a close handler doesn't free the socket…
Jarred-Sumner Oct 24, 2026
95bf230
test(tls): print what the kernel knows about a connection that doesn'…
Jarred-Sumner Oct 24, 2026
2748adb
test(tls): the rows that expect a reset don't depend on the kernel de…
Jarred-Sumner Oct 24, 2026
dcce69c
test(tls): remove the temporary output
Jarred-Sumner Oct 24, 2026
d101df6
tls: end() delivers what was written, also when end() is called again…
Jarred-Sumner Oct 25, 2026
67a5247
Revert "tls: end() delivers what was written, also when end() is call…
Jarred-Sumner Oct 25, 2026
0688800
tls: end() delivers what was written, also when end() is called again…
Jarred-Sumner Oct 25, 2026
ab862b7
TLS: end() and shutdown() deliver what was written, also when the ser…
Jarred-Sumner Oct 25, 2026
578cb48
test(socket): the listener stops once the reader has made room and ho…
Jarred-Sumner Oct 25, 2026
022bac3
test(socket): hold the state that the deadline tests count sweeps in,…
Jarred-Sumner Oct 25, 2026
8417dd1
node:https: the listener always gets the server's own TLS context, li…
Jarred-Sumner Oct 25, 2026
123f024
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 27, 2026
734ba6f
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 27, 2026
57cbd9b
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 28, 2026
9b89d62
net, tls, http: follow the tables and stored outputs of the ahead-of-…
Jarred-Sumner Oct 28, 2026
00c2eba
usockets: allocate the table of owned descriptors at the first socket
Jarred-Sumner Oct 28, 2026
bfd6c16
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 28, 2026
3c5c494
tls: a server does not name the default CA list to its clients
Jarred-Sumner Oct 29, 2026
fda65fa
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 29, 2026
f808241
test: the fixture of the client CA names rejects when a handshake fails
Jarred-Sumner Oct 29, 2026
5b5f2ab
test: the connect({path}) leak test counts the pages of small blocks
Jarred-Sumner Oct 29, 2026
dfc0b55
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 29, 2026
df0bb0b
test: the differential test of the built-ins calls the raw setDefault…
Jarred-Sumner Oct 29, 2026
5810f7b
test: the HTTP/2 idle timeout tests wait for the close that is on its…
Jarred-Sumner Oct 29, 2026
22e03b3
test: write the IR text of custom-accessor-reached-computed-key anew
Jarred-Sumner Oct 29, 2026
9a80c54
test: the HTTP/2 idle timeout tests ask the session whether it is sti…
Jarred-Sumner Oct 29, 2026
69d874e
node:http: private.d.ts no longer declares three getters that the nat…
Jarred-Sumner Oct 29, 2026
a478b3c
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Oct 29, 2026
bc45fb4
trim: TlsSockets guards the TLS connection object of a node:https server
Jarred-Sumner Oct 29, 2026
865fdd4
Merge branch 'main' into claude/net-tls-consolidated
Jarred-Sumner Nov 2, 2026
ddfd2d3
net: the members of the write request get names of their own
Jarred-Sumner Nov 2, 2026
c2cee80
Merge branch 'main' into claude/net-tls-consolidated
claude Nov 11, 2026
b4b68fe
aot table: upgradeDuplexToTLS takes any options again, so the differe…
claude Nov 11, 2026
476bd3d
socket: call_write_complete is not generic over SSL
claude Nov 11, 2026
0cea581
Bun.serve: a server without TLS calls no function of the TLS code
claude Nov 11, 2026
bcd15b2
node:dgram: build the error of a failed receive without an object lit…
claude Nov 11, 2026
ddd8089
test: the IR counts and the small-view calls follow the table of buil…
claude Nov 11, 2026
4ce1bdd
trim: the constructor behind Bun.redis goes with the Redis client
claude Nov 11, 2026
b6f9327
Merge branch 'main' into claude/net-tls-consolidated
claude Nov 11, 2026
ba79d73
https: a server registers three listeners fewer
claude Nov 11, 2026
ee30885
node:net, node:dgram: the errors of a failed listen or bind have a mo…
claude Nov 11, 2026
a2fff67
node:http: a server sets socket.server from a listener of its own again
claude Nov 11, 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
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion docs/runtime/http/server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,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 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
2 changes: 1 addition & 1 deletion docs/runtime/networking/fetch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ An option given on the request takes precedence over the session's, `proxy: fals

The constructor throws for a `proxy` or a `tls.checkServerIdentity` that it cannot use, such as `proxy: 8080`, a `proxy` object without a `url`, or a `checkServerIdentity` that is not a function. For `proxy`, only `undefined`, `null` and `""` mean no option. On a request, `fetch` ignores such a `proxy` (`true` throws), and the request keeps the proxy of its session or of the environment.

Bun rounds `idleTimeout` up to its socket timer, which moves in 4 second steps up to four minutes and in whole minutes beyond that. A connection is never closed before `idleTimeout`, and it can stay open for up to two steps longer. The longest `idleTimeout` is 238 minutes. A larger value means 238 minutes.
Bun checks socket timers every 4 seconds. A connection is never closed before `idleTimeout`, and it can stay open up to 4 seconds longer.

Without `maxIdleSockets` a session has no limit of its own. The idle connections of all sessions and of plain `fetch` share one bounded pool per kind. When that pool is full, Bun closes the longest-idle connection in it, whichever session it belongs to. A `maxIdleSockets` above the size of the pool has no effect.

Expand Down
6 changes: 4 additions & 2 deletions docs/runtime/networking/tcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const socket = await Bun.connect({
});
```

To require TLS, specify `tls: true`.
To require TLS, specify `tls: true` or a `tls` options object. An object that sets no option, such as `{}`, is the same as `tls: true`.

```ts
// The client
Expand Down Expand Up @@ -182,7 +182,9 @@ socket.reload({

## Buffering

TCP sockets in Bun do not buffer data, so performance-sensitive code should buffer writes itself. For example, this:
`socket.write()` does not buffer data. It returns the number of bytes the socket accepted, and you send the rest from the `drain` handler. `socket.end(data)` accepts the whole chunk: Bun queues what the socket didn't accept, sends it once the socket is writable again, and then closes the socket. If the queued data can't be sent, `close(socket, error)` gets the error: `EPIPE` or `ECONNRESET` when the peer is gone, `ETIMEDOUT` when the peer reads nothing for the socket's timeout (`socket.timeout(seconds)`, 120 seconds by default on a socket that `Bun.listen` accepted).

Performance-sensitive code should buffer writes itself. For example, this:

```ts
socket.write("h");
Expand Down
6 changes: 3 additions & 3 deletions docs/runtime/nodejs-compat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ We update this page regularly. It reflects the latest version of Bun's compatibi

### [`node:dns`](https://nodejs.org/api/dns.html)

🟢 Fully implemented. Missing `resolveTlsa`. Bun ignores the `Resolver` `maxTimeout` option.
🟢 Fully implemented. Missing `resolveTlsa`. Bun ignores the `Resolver` `maxTimeout` option. On the main thread, `setDefaultResultOrder()` also sets which address family `fetch()` and `Bun.connect()` dial first. That part applies to the whole process, including worker threads, because they share one DNS cache. In a worker thread, it only changes that thread's `dns.lookup()` default.

### [`node:events`](https://nodejs.org/api/events.html)

Expand All @@ -49,7 +49,7 @@ We update this page regularly. It reflects the latest version of Bun's compatibi

### [`node:https`](https://nodejs.org/api/https.html)

🟡 `request`, `get`, `Agent` and `globalAgent` are implemented, including connection pooling. Client sockets are `tls.TLSSocket`s. `https.Server` is `http.Server` with TLS options rather than a `tls.Server`. Its request sockets (`req.socket`) are not `tls.TLSSocket`s: `encrypted`, `authorized` and `servername` work, but `getPeerCertificate()` and `getCipher()` are missing. `setSecureContext()`, `addContext()`, `SNICallback` and `handshakeTimeout` are not supported.
🟡 `request`, `get`, `Agent` and `globalAgent` are implemented, including connection pooling. `https.Server` extends `http.Server` rather than `tls.Server`, and has `setSecureContext()`, `addContext()`, `getTicketKeys()`, `setTicketKeys()` and `SNICallback`. Client sockets and request sockets (`req.socket`) are `tls.TLSSocket`s. The server does not negotiate ALPN, so `ALPNProtocols` and `ALPNCallback` have no effect and `req.socket.alpnProtocol` is `false`. The `'keylog'` event is not supported, and `'tlsClientError'` is only emitted for an `SNICallback` error and for a handshake that takes longer than `handshakeTimeout`.

### [`node:os`](https://nodejs.org/api/os.html)

Expand Down Expand Up @@ -141,7 +141,7 @@ We update this page regularly. It reflects the latest version of Bun's compatibi

### [`node:tls`](https://nodejs.org/api/tls.html)

🟡 Missing `pskCallback`, OCSP stapling (`requestOCSP`), the server `'newSession'`/`'resumeSession'` events and session ticket keys (`ticketKeys` is ignored). As a result, session resumption does not work across processes. Bun uses BoringSSL, so `tlsSocket.renegotiate()` always fails and `getEphemeralKeyInfo()`/`getSharedSigalgs()` return no information.
🟡 Missing `pskCallback`, OCSP stapling (`requestOCSP`) and the server `'newSession'`/`'resumeSession'` events. Bun uses BoringSSL, so `tlsSocket.renegotiate()` always fails and `getSharedSigalgs()` returns no information.

### [`node:util`](https://nodejs.org/api/util.html)

Expand Down
99 changes: 75 additions & 24 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5138,6 +5138,21 @@ declare module "bun" {
*/
secureOptions?: number | undefined; // Value is a numeric bitmask of the `SSL_OP_*` options

/**
* The minimum TLS protocol version to allow, by the same names as `node:tls`.
*
* @default "TLSv1.2"
*/
minVersion?: "TLSv1" | "TLSv1.1" | "TLSv1.2" | "TLSv1.3" | undefined;

/**
* The maximum TLS protocol version to allow. A `maxVersion` below
* `"TLSv1.2"` also needs a `minVersion`, because the default minimum is `"TLSv1.2"`.
*
* @default "TLSv1.3"
*/
maxVersion?: "TLSv1" | "TLSv1.1" | "TLSv1.2" | "TLSv1.3" | undefined;

ALPNProtocols?: string | BufferSource;

ciphers?: string;
Expand Down Expand Up @@ -7139,10 +7154,13 @@ declare module "bun" {
* The socket remains readable until the remote end also closes its write side or the connection is terminated.
* This sends a TCP FIN packet after writing the data.
*
* Unlike `write()`, `end(data)` accepts the whole chunk. Bytes the socket can't take right away are queued and sent
* before the FIN.
*
* @param data Optional final data to write before closing. Same types as `write()`.
* @param byteOffset Optional offset for buffer data.
* @param byteLength Optional length for buffer data.
* @returns The number of bytes written for the final chunk. Returns `-1` if the socket was already closed or shutting down.
* @returns The byte length of the final chunk. Returns `-1` if the socket was already closed or shutting down.
* @example
* ```ts
* // send some data and close the write side
Expand All @@ -7167,11 +7185,14 @@ declare module "bun" {
ref(): void;

/**
* Set a timeout until the socket automatically closes.
* Call the {@link SocketHandler.timeout `timeout`} handler once, `seconds` seconds from now.
*
* To reset the timeout, call this function again.
* The socket stays open. Call {@link end `end()`} or {@link terminate `terminate()`} in the handler to close it.
* Both drop the data an earlier `end(data)` queued and the peer hasn't taken yet.
*
* When a timeout happens, the `timeout` callback is called and the socket is closed.
* Reads and writes don't restart the timer. To reset the timeout, call this function again. `0` cancels it.
*
* Bun checks socket timeouts every 4 seconds, so the handler can run up to 4 seconds late. It never runs early.
*/
timeout(seconds: number): void;

Expand Down Expand Up @@ -7585,6 +7606,11 @@ declare module "bun" {

interface TLSUpgradeOptions<Data> {
data?: Data;
/**
* TLS configuration for the upgraded socket.
*
* On a client socket, an object that sets no option, such as `{}`, is the same as `true`.
*/
tls: TLSOptions | boolean;
socket: SocketHandler<Data>;
}
Expand Down Expand Up @@ -7622,11 +7648,16 @@ declare module "bun" {
* handler, this is called only after the handshake completes.
*/
open?(socket: Socket<Data>): void | Promise<void>;
/**
* Called once the socket has closed. `error` is set when the connection
* failed: a read reported a reset (`syscall: "read"`), or the peer was gone
* before the data queued by `end(data)` was sent (`syscall: "write"`).
*/
close?(socket: Socket<Data>, error?: Error): void | Promise<void>;
/**
* Called when a handler throws, and when an established TLS session fails
* (a record that does not decrypt, a fatal alert from the peer). After a
* TLS failure the socket closes, with or without this handler.
* (a record that does not decrypt, a fatal alert from the peer). After a TLS
* failure the socket closes, with or without this handler.
*/
error?(socket: Socket<Data>, error: Error): void | Promise<void>;
data?(socket: Socket<Data>, data: BinaryTypeList[DataBinaryType]): void | Promise<void>;
Expand Down Expand Up @@ -7661,7 +7692,8 @@ declare module "bun" {
connectError?(socket: Socket<Data>, error: Error): void | Promise<void>;

/**
* Called when a message times out.
* Called when the timeout set by {@link Socket.timeout `socket.timeout()`} expires. A socket accepted by
* `Bun.listen` starts with a 120 second timeout. The socket stays open.
*/
timeout?(socket: Socket<Data>): void | Promise<void>;
/**
Expand Down Expand Up @@ -7728,6 +7760,20 @@ declare module "bun" {
* @default false
*/
exclusive?: boolean;
/**
* Set `SO_REUSEPORT` so multiple processes can bind to the same port, which is useful for load balancing.
*
* Ignored when {@link exclusive} is `true`.
*
* @default false
*/
reusePort?: boolean;
/**
* Set `IPV6_V6ONLY` so a socket bound to an IPv6 address doesn't also accept IPv4 connections.
*
* @default false
*/
ipv6Only?: boolean;
/**
* Whether to allow half-open connections.
*
Expand Down Expand Up @@ -7756,22 +7802,24 @@ declare module "bun" {
*/
port: number;
/**
* TLS configuration with which to create the socket
* TLS configuration with which to create the socket.
*
* `true` or any options object connects with TLS. An object that sets no
* option, such as `{}`, is the same as `true`. Omit `tls` or pass `false`
* to connect without TLS.
*/
tls?: TLSOptions | boolean;
/**
* Whether to use exclusive mode.
*
* When set to `true`, the socket binds exclusively to the specified address:port
* combination, preventing other processes from binding to the same port.
*
* When `false` (default), other sockets may be able to bind to the same port
* depending on the operating system's socket sharing capabilities and settings.
*
* @default false
* @deprecated Has no effect on a client socket. This is a {@link Bun.listen} option (see {@link TCPSocketListenOptions}).
*/
exclusive?: boolean;
/**
* @deprecated Has no effect on a client socket. This is a {@link Bun.listen} option (see {@link TCPSocketListenOptions}).
*/
reusePort?: boolean;
/**
* @deprecated Has no effect on a client socket. This is a {@link Bun.listen} option (see {@link TCPSocketListenOptions}).
*/
ipv6Only?: boolean;
}

Expand All @@ -7782,14 +7830,20 @@ declare module "bun" {
unix: string;

/**
* TLS configuration with which to create the socket
* TLS configuration with which to create the socket.
*
* For {@link Bun.connect}, `true` or any options object connects with TLS.
* An object that sets no option, such as `{}`, is the same as `true`.
*/
tls?: TLSOptions | boolean;
}

interface FdSocketOptions<Data = undefined> extends SocketOptions<Data> {
/**
* TLS configuration with which to create the socket
* TLS configuration with which to create the socket.
*
* `true` or any options object connects with TLS. An object that sets no
* option, such as `{}`, is the same as `true`.
*/
tls?: TLSOptions | boolean;
/**
Expand Down Expand Up @@ -9420,11 +9474,8 @@ declare module "bun" {
| {
/**
* Seconds an idle connection stays in the pool before it is closed.
* The socket timer is coarse: it moves in 4 second steps up to four
* minutes, and in whole minutes beyond that. Bun rounds the value up
* to the timer, so a connection is never closed before `idleTimeout`,
* and it can stay open for up to two steps longer. The longest is
* 238 minutes (14280), and a larger value means that.
* Bun checks socket timers every 4 seconds, so a connection is never
* closed before `idleTimeout` and can stay open up to 4 seconds longer.
*
* @default 300
*/
Expand Down
Loading
Loading