Title: tls: `tls.connect()` silently ignores `keepAlive`, `keepAliveInitialDelay`, and `noDelay` options · Issue #62003 · nodejs/node · GitHub
Open Graph Title: tls: `tls.connect()` silently ignores `keepAlive`, `keepAliveInitialDelay`, and `noDelay` options · Issue #62003 · nodejs/node
X Title: tls: `tls.connect()` silently ignores `keepAlive`, `keepAliveInitialDelay`, and `noDelay` options · Issue #62003 · nodejs/node
Description: Version v24.1.0 (also affects main; present since at least v20.x) Platform Darwin arm64 (but the platform is irrelevant to the issue) Subsystem tls What steps will reproduce the bug? Run the following script and observe difference betwee...
Open Graph Description: Version v24.1.0 (also affects main; present since at least v20.x) Platform Darwin arm64 (but the platform is irrelevant to the issue) Subsystem tls What steps will reproduce the bug? Run the follow...
X Description: Version v24.1.0 (also affects main; present since at least v20.x) Platform Darwin arm64 (but the platform is irrelevant to the issue) Subsystem tls What steps will reproduce the bug? Run the follow...
Opengraph URL: https://github.com/nodejs/node/issues/62003
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"tls: `tls.connect()` silently ignores `keepAlive`, `keepAliveInitialDelay`, and `noDelay` options","articleBody":"### Version\n\nv24.1.0 (also affects main; present since at least v20.x)\n\n### Platform\n\n```text\nDarwin arm64 (but the platform is irrelevant to the issue)\n```\n\n### Subsystem\n\ntls\n\n### What steps will reproduce the bug?\n\nRun the following script and observe difference between `net.connect()` and `tls.connect()` keepAlive options:\n\n```js\nnet.connect() - socket[kSetNoDelay]: true\nnet.connect() - socket[kSetKeepAlive]: true\nnet.connect() - socket[kSetKeepAliveInitialDelay]: 5\ntls.connect() - socket[kSetNoDelay]: false\ntls.connect() - socket[kSetKeepAlive]: false\ntls.connect() - socket[kSetKeepAliveInitialDelay]: 0\n```\n\n```js\nimport tls from \"node:tls\";\nimport net from \"node:net\";\nimport assert from \"node:assert\";\nimport { execSync } from \"node:child_process\";\nimport fs from \"node:fs\";\n\nexecSync(\n 'openssl req -new -x509 -newkey rsa:2048 -nodes -keyout /tmp/k.pem -out /tmp/c.pem -days 1 -subj \"/CN=localhost\" 2\u003e/dev/null',\n);\n\nconst tmp = new net.Socket();\nconst syms = Object.getOwnPropertySymbols(tmp);\nconst kSetNoDelay = syms.find((s) =\u003e s.toString() === \"Symbol(kSetNoDelay)\");\nconst kSetKeepAlive = syms.find(\n (s) =\u003e s.toString() === \"Symbol(kSetKeepAlive)\",\n);\nconst kSetKeepAliveInitialDelay = syms.find(\n (s) =\u003e s.toString() === \"Symbol(kSetKeepAliveInitialDelay)\",\n);\ntmp.destroy();\n\nconst opts = { keepAlive: true, keepAliveInitialDelay: 5000, noDelay: true };\n\nconst netServer = net.createServer((s) =\u003e s.end());\nnetServer.listen(0, () =\u003e {\n const socket = net.connect({ port: netServer.address().port, ...opts });\n socket.on(\"connect\", () =\u003e {\n console.log(\"net.connect() - socket[kSetNoDelay]:\", socket[kSetNoDelay]); // true\n console.log(\n \"net.connect() - socket[kSetKeepAlive]:\",\n socket[kSetKeepAlive], // true\n );\n console.log(\n \"net.connect() - socket[kSetKeepAliveInitialDelay]:\",\n socket[kSetKeepAliveInitialDelay], // 5s\n );\n socket.destroy();\n netServer.close();\n testTls();\n });\n});\n\nfunction testTls() {\n const srv = tls.createServer(\n {\n key: fs.readFileSync(\"/tmp/k.pem\"),\n cert: fs.readFileSync(\"/tmp/c.pem\"),\n },\n (s) =\u003e s.end(),\n );\n srv.listen(0, () =\u003e {\n const socket = tls.connect({\n port: srv.address().port,\n rejectUnauthorized: false,\n ...opts,\n });\n socket.on(\"secureConnect\", () =\u003e {\n console.log(\"tls.connect() - socket[kSetNoDelay]:\", socket[kSetNoDelay]); // false\n console.log(\n \"tls.connect() - socket[kSetKeepAlive]:\",\n socket[kSetKeepAlive], // false\n );\n console.log(\n \"tls.connect() - socket[kSetKeepAliveInitialDelay]:\",\n socket[kSetKeepAliveInitialDelay], // 0\n );\n socket.destroy();\n srv.close();\n });\n });\n}\n```\n\n### How often does it reproduce? Is there a required condition?\n\nAlways. Any call to `tls.connect()` with `keepAlive`, `keepAliveInitialDelay`, or `noDelay` options will silently ignore them.\n\nLong-lived TLS connections through infrastructure with idle timeouts (load balancers, NAT gateways, firewalls, AWS PrivateLink) silently break when `keepalive` is not applied. The connection is closed by an intermediary during an idle period, and the response never reaches the client. This is difficult to diagnose because `tls.connect()` accepts the options without error.\n\n### What is the expected behavior? Why is that the expected behavior?\n\n`tls.connect()` should honor `keepAlive`, `keepAliveInitialDelay`, and `noDelay` options, applying them to the underlying TCP socket (i.e., `setsockopt(SO_KEEPALIVE)` / `setsockopt(TCP_NODELAY)` should be called).\n\nThe [documentation](https://nodejs.org/docs/latest/api/tls.html#tlsconnectoptions-callback) states that `tls.connect()` accepts:\n```\n...: Any socket.connect() option not already listed.\n```\n\n`socket.connect()` documents `keepAlive`, `keepAliveInitialDelay`, and `noDelay` as supported options. `net.createConnection()` with the same options works correctly.\n\n### What do you see instead?\n\nThe options are silently dropped. `setsockopt(SO_KEEPALIVE)` is never called on the underlying socket.\n\n### Additional information\n\nThe options are lost at two filtering points in `lib/internal/tls/wrap.js`:\n\n1. `tls.connect() → new TLSSocket()` ([lines 1743-1758](https://github.com/nodejs/node/blob/main/lib/internal/tls/wrap.js#L1743-L1758))\n\n`tls.connect()` constructs a `TLSSocket` with a hardcoded subset of options. `keepAlive`, `keepAliveInitialDelay`, and `noDelay` are not included:\n\n```js\nconst tlssock = new TLSSocket(options.socket, {\n allowHalfOpen: options.allowHalfOpen,\n pipe: !!options.path,\n secureContext: context,\n isServer: false,\n requestCert: true,\n rejectUnauthorized: options.rejectUnauthorized !== false,\n session: options.session,\n ALPNProtocols: options.ALPNProtocols,\n requestOCSP: options.requestOCSP,\n enableTrace: options.enableTrace,\n pskCallback: options.pskCallback,\n highWaterMark: options.highWaterMark,\n onread: options.onread,\n signal: options.signal,\n // keepAlive, keepAliveInitialDelay, noDelay not forwarded\n});\n```\nNote: `keepAlive` is read at [line 1727](https://github.com/nodejs/node/blob/main/lib/internal/tls/wrap.js#L1727), but only to set `singleUse` - it is not forwarded to the socket layer.\n\n2. `TLSSocket constructor → net.Socket` super call ([lines 585-593](https://github.com/nodejs/node/blob/main/lib/internal/tls/wrap.js#L585-L593))\n\nEven if the options were forwarded to `TLSSocket`, the `net.Socket` super-constructor call also uses a hardcoded subset that omits them:\n\n```js\nFunctionPrototypeCall(net.Socket, this, {\n handle: ...,\n allowHalfOpen: ...,\n pauseOnCreate: tlsOptions.pauseOnConnect,\n manualStart: true,\n highWaterMark: tlsOptions.highWaterMark,\n onread: !socket ? tlsOptions.onread : null,\n signal: tlsOptions.signal,\n // keepAlive, keepAliveInitialDelay, noDelay not forwarded\n});\n```\n\nThe `net.Socket` constructor ([lib/net.js:475-476](https://github.com/nodejs/node/blob/main/lib/net.js#L475-L476)) sets `kSetNoDelay`, `kSetKeepAlive`, and `kSetKeepAliveInitialDelay` from the options, and these are later applied in `afterConnect()`. Since the options are never passed through, the symbols remain `false/0`.\n\n```js\nthis[kSetNoDelay] = Boolean(options.noDelay);\nthis[kSetKeepAlive] = Boolean(options.keepAlive);\n```\n\nAt the same time `net.createConnection()` works: `net.connect()` creates `new Socket(options)` with the **full** options object, so the constructor picks up all three options and stores them as symbols that `afterConnect` applies.\n\nFix: Both filtering points need to forward `keepAlive`, `keepAliveInitialDelay`, and `noDelay`.","author":{"url":"https://github.com/tadjik1","@type":"Person","name":"tadjik1"},"datePublished":"2026-02-26T10:07:06.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/62003/node/issues/62003"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:219089ce-d56d-0de0-ff97-8848ad8c6b5a |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 8792:30A7B5:56EF21:77D442:6A4C8801 |
| html-safe-nonce | ed133046e0e759fb216478a355e57fd9ab82bff9c3e7909cb9ff70c5462ba7d5 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4NzkyOjMwQTdCNTo1NkVGMjE6NzdENDQyOjZBNEM4ODAxIiwidmlzaXRvcl9pZCI6IjY4MzIyMjI2MzM4MDQ0NjYxNzciLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 186e9e3a846807d7ebd04570d122cbc521eaa72258e656f5eea9b63fb6f74f9f |
| hovercard-subject-tag | issue:3994560853 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/62003/issue_layout |
| twitter:image | https://opengraph.githubassets.com/a99223e284dc65b11708d0a9a787fbcc96469ed2ec44d9b9a9fdbe329f21911b/nodejs/node/issues/62003 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/a99223e284dc65b11708d0a9a787fbcc96469ed2ec44d9b9a9fdbe329f21911b/nodejs/node/issues/62003 |
| og:image:alt | Version v24.1.0 (also affects main; present since at least v20.x) Platform Darwin arm64 (but the platform is irrelevant to the issue) Subsystem tls What steps will reproduce the bug? Run the follow... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | tadjik1 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3d11bb817438277de2a940854450e83a7d32b6aeb5014e9e6b00a6423900251c |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7a2dede45637df6b92ddcfe2a557e67d4b5854ae |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width