Repository navigation
"localhost" favours IPv6 in node v17, used to favour IPv4 #40537
Description
Version
v17.0.0
Platform
Darwin pink.local 20.6.0 Darwin Kernel Version 20.6.0: Mon Aug 30 06:12:21 PDT 2021; root:xnu-7195.141.6~3/RELEASE_X86_64 x86_64
Subsystem
No response
What steps will reproduce the bug?
var http = require('http')
var server = http.createServer(function (req, res) { /* ... */ })
server.listen(3000, 'localhost', function (_err) {
console.log('server listening: %s', server.address())
})I'm not sure what the underlying mechanism is, but it looks to me like the resolution of "localhost" for server.listen(PORT, HOST, ...) and for http.request('http://localhost/...', ...) changed from favouring IPv4 in node v16 and earlier, to favouring IPv6. The result is some possibly confusing breakages when using IPv4 values such as 127.0.0.1 and 0.0.0.0. For example, the following script errors with node v17, but succeeds with node v16:
// example-localhost-means-which-ipv.js
var http = require('http')
var server = http.createServer(function (req, res) {
req.on('data', function (chunk) {
console.log('server req data: %s', chunk)
})
req.on('end', function () {
console.log('server req end')
res.end('pong')
})
})
// Listen on IPv4 address.
var theHost = '127.0.0.1'
server.listen(3000, theHost, function (_err) {
console.log('server listening: %s', server.address())
// GET from localhost.
var theUrl = 'http://localhost:3000/ping'
console.log('client req: GET %s', theUrl)
http.get(theUrl, function (res) {
console.log('client res:', res.statusCode, res.headers)
res.on('data', (chunk) => {
console.log('client data: %s', chunk)
})
res.on('end', () => {
console.log('client end')
server.close()
})
})
})% node --version
v17.0.0
% node example-localhost-means-which-ipv.js
server listening: { address: '127.0.0.1', family: 'IPv4', port: 3000 }
client req: GET http://localhost:3000/ping
node:events:368
throw er; // Unhandled 'error' event
^
Error: connect ECONNREFUSED ::1:3000
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1161:16)
Emitted 'error' event on ClientRequest instance at:
at Socket.socketErrorListener (node:_http_client:447:9)
at Socket.emit (node:events:390:28)
at emitErrorNT (node:internal/streams/destroy:164:8)
at emitErrorCloseNT (node:internal/streams/destroy:129:3)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '::1',
port: 3000
}
Node.js v17.0.0
% nvm use 16
Now using node v16.11.1 (npm v8.0.0)
% node example-localhost-means-which-ipv.js
server listening: { address: '127.0.0.1', family: 'IPv4', port: 3000 }
client req: GET http://localhost:3000/ping
server req end
client res: 200 {
date: 'Wed, 20 Oct 2021 21:01:56 GMT',
connection: 'close',
'content-length': '4'
}
client data: pong
client end
Similarly, if we reverse things to listen on 'localhost' and GET from 'http://[::1]:3000/ping' (IPv6), then it fails on node v16 and passes on node v17.
As I said above, I don't know the mechanism for selecting IPv4 or IPv6. I suspect this isn't a "bug", except perhaps a lack of documentation? I don't see any mention of localhost of IPv6 in the changelog. Thanks.
How often does it reproduce? Is there a required condition?
Everytime. While I tested mostly on macOS, I believe I was seeing this in GitHub Action tests running on linux containers.
What is the expected behavior?
No response
What do you see instead?
No response
Additional information
No response
Activity
trentm commentedon Oct 20, 2021
If it helps, I happened upon this is tests using Hapi. A Hapi Server listen address defaults to
'0.0.0.0'which pins it to IPv4. When my test made a client request usinglocalhostI saw the aboveECONNREFUSED ::1:$PORT.richardlau commentedon Oct 20, 2021
It's #39987 -- Node.js now returns IP addresses in the order they are returned from the name resolver/DNS. There's a
--dns-result-ordercommand line option you can set to control the behaviour -- the difference in Node.js 17 is the default changed.hapi test: use localhost rather than '0.0.0.0' default to match clien…
trentm commentedon Oct 20, 2021
Ah, which is the first "Other notable changes" in the release notes. 🤦 Thanks for the pointer! I'll close this, as it is definitely intended behaviour. A mention of this in some sort of "node v17 migration guide" would likely be helpful to others.
Fix scala-js#12: Force the server and client of Node.js Com to use IPv4.
Treat response.text() as a promise in error handling. (#11)
Allow specifying hostname for Hapi servers.
Allow specifying hostname for Hapi servers. (#12)
Server: correctly print URL when running on IPv6
test(node18-ipv6): change default host from localhost to 127.0.0.1
macrozone commentedon Sep 5, 2022
Just run into this.
one fix is to do this:
which reverts the old order
test: can run with node17
test: can run with node17
test: can run with node17
17 remaining items
Use `127.0.0.1` instead of `localhost`
Use `127.0.0.1` instead of `localhost`
undergroundwires commentedon Mar 30, 2024
Here's my workaround (open-source and documented) that I hope that can help you too:
force-ipv4.sh.: Script for general use.After days of research and trial/error, this is how I got this working:
force-ipv4.sh, that configures system to prefer IPv4 over IPv6, call it to configure the machine. It was not easy to find a reliable cross-platform solution and I went Cloudflare WARP for DNS resolution along with some system configurations.force-ipv4and call it in GitHub runners.fetchAPI from Node.Related commit introducing this fix: undergroundwires/privacy.sexy@52fadcd
ci/cd: bump Node.js environment to 20.x
ci/cd: bump Node.js environment to 20.x
add node option
force IPv4 localhost for node 17+
Fix PM2 cluster mode ECONNREFUSED errors
Fix PM2 cluster mode ECONNREFUSED errors
Fix PM2 cluster mode ECONNREFUSED errors
fix: use localhost instead of 127.0.0.1 and fix Windows spawn error
fix: 修正 Windows 相容性與 IPv6 連線問題 (#1)
Ds merge aura84 20260922 (#1)
Ds merge aura84 bens changes 20260922 (#2)
Merge Dspace84 conflicts fixed 20260930 (#2)