Skip to content

fetch: after a server sends Connection: close, each new connection serves one request (regression in 7.15.0, fixed in 8.11.0, still in 7.30.0 / Node 24) #5910

Open

Description

@NITBRASIL

Bug Description

With fetch from undici 7.15.0 through 7.30.0, once a server closes a keep-alive connection with Connection: close, subsequent connections are used for a single request each. undici.request is not affected, and neither is fetch in 7.14.0 or 8.11.0.

Node.js v24 bundles an affected version (v24.18.0 ships undici 7.28.0), so global fetch on Node 24 LTS is affected.

Real-world impact: a test suite talking to a local Supabase stack (Kong/nginx closes each keep-alive socket after keepalive_requests = 100) opened 16,331 TCP connections for 17,920 requests and failed on macOS with EADDRNOTAVAIL / UND_ERR_SOCKET once ephemeral ports ran out. The same suite on Node 22 (undici 6.x) reuses sockets normally.

Reproducible By

server.mjs: closes each socket after 100 requests and reports how many requests each socket served:

import http from "node:http";
let ids = 0; const per = {};
http.createServer((req, res) => {
  const s = req.socket; s.n = (s.n ?? 0) + 1; s.id ??= ++ids; per[s.id] = s.n;
  if (req.url === "/stats") return res.end(JSON.stringify(Object.values(per).slice(0, 8)));
  if (s.n >= 100) res.setHeader("connection", "close");
  res.end("ok");
}).listen(Number(process.argv[2]));

client.mjs: 400 sequential fetches with the body fully consumed:

import { fetch } from "undici";
const url = `http://127.0.0.1:${process.argv[2]}/`;
for (let i = 0; i < 400; i++) await (await fetch(url)).text();
console.log(await (await fetch(url + "stats", { headers: { connection: "close" } })).text());
node server.mjs 47000 &
node client.mjs 47000

Server and client run in separate processes. The server's Node version does not matter (same result on 22 and 24).

Expected Behavior

Each connection serves 100 requests: [100,100,100,100,1] (the final 1 is the /stats request).

Actual Behavior

After the second close, each new connection serves one request: [100,100,1,1,1,1,1,1] (400 requests → ~202 connections).

client [requests per socket]
undici 6.29.0 fetch [100,100,100,100,1]
undici 7.14.0 fetch [100,100,100,100,1]
undici 7.15.0 fetch [100,100,1,1,1,1,1,1]
undici 7.30.0 fetch [100,100,1,1,1,1,1,1]
undici 8.0.0 – 8.1.0 fetch [100,100,1,1,1,1,1,1]
undici 8.2.0 – 8.10.0 fetch [100,100,1,100,100]
undici 8.11.0 / 8.11.2 fetch [100,100,100,100,1]
undici 7.30.0 / 8.11.2 request [100,100,100,100,1]
node:http Agent keepAlive (Node 24) [100,100,100,100,1,…]
Node 24.18.0 global fetch (undici 7.28.0) [100,100,1,1,1,1,1,1]

Bisected: last good 7.14.0, first bad 7.15.0. Results are identical on Node 22.22.3 and 24.18.0 for the npm package.

Environment

macOS 26 (Darwin 25.5.0, arm64), Node.js v22.22.3 and v24.18.0.

Additional context

It looks fixed on main as of 8.11.0 (8.2.0 fixed it partially). Could the fix be backported to v7.x so it reaches Node.js 24?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions