Fixing Node.js Localhost ECONNREFUSED Connection Errors

Quick Answer: Node 17 broke local connections by switching from hardcoded IPv4 (127.0.0.1) lookups to the operating system’s default order, which prioritizes IPv6 (::1). If your server listened only on IPv4, connections failed immediately. Node 20 resolved this by implementing the "Happy Eyeballs" algorithm, which automatically falls back to IPv4 if IPv6 fails.
I’ve run into a frustrating issue more than once: upgrading a Node version only to find that a local service suddenly throws ECONNREFUSED on a port that is clearly open. The server is running, the configuration looks right, but the connection fails. This headache started with Node 17, which changed how Node.js resolves localhost. Here is why this broke and how Node 20 resolves the issue.
Why did Node 17 cause connection refused errors on localhost?
Node 17 stopped hardcoding IPv4 (127.0.0.1) as the default fallback for localhost and began respecting the operating system’s default lookup order, which prioritizes IPv6 (::1). If a local server only listens on IPv4, the client-side Node process attempts to connect via IPv6, gets refused immediately, and fails the connection.
When I look at how operating systems resolve names, a machine typically has two loopback addresses: the IPv4 address 127.0.0.1 and the IPv6 address ::1. Modern operating systems prioritize the IPv6 address. In Node 16, the runtime quietly reordered these DNS results to put IPv4 first. Node 17 removed this reordering to align with standard OS behavior.
If a backend service (like a database or an API) binds strictly to the IPv4 address, Node 17's client attempts to connect to ::1 first. When that connection is refused, Node 17 simply stops and throws an error instead of trying the next address in the list.
How does Node 20's Happy Eyeballs algorithm fix localhost?
Node 20 resolves this by implementing the Happy Eyeballs algorithm (RFC 8305), which manages dual-stack connections by racing IPv6 and IPv4. If the preferred IPv6 connection fails immediately or takes longer than 250 milliseconds, Node automatically falls back to IPv4.
The Happy Eyeballs algorithm makes dual-stack connections seamless. Instead of waiting indefinitely or failing immediately, the client initiates a fast fallback. Think of it like trying to enter a house with two doors. I might knock on the IPv6 door first. If that door is locked, or if there is no response within 250 milliseconds, I do not give up; I immediately try the IPv4 door. Node 20 does exactly this, allowing local development to work regardless of which IP format the server binds to.
| Node.js Version | Default Resolution Order | Behavior on IPv6 Connection Failure | Result for IPv4-only Local Servers |
|---|---|---|---|
| Node 16 and below | Hardcoded IPv4 (127.0.0.1) first |
N/A (rarely hit IPv6 first) | Works seamlessly |
| Node 17 to 19 | OS Default (usually IPv6 ::1 first) |
Fails immediately with ECONNREFUSED |
Broken local connections |
| Node 20 and above | OS Default (IPv6 first via Happy Eyeballs) | Falls back to IPv4 after 250ms (or on refusal) | Works seamlessly |
How can you resolve localhost connection issues without upgrading?
If upgrading to Node 20 is not an option, you can resolve the issue by binding your local servers to the wildcard address 0.0.0.0 or by using the explicit IP address 127.0.0.1 in your client connection strings instead of the localhost hostname.
When I configure a local server to listen on 127.0.0.1, it only accepts IPv4 traffic. If I cannot upgrade Node, I change the server's binding configuration to 0.0.0.0 or :: (which binds to all interfaces). Alternatively, changing the client connection string from http://localhost:3000 to http://127.0.0.1:3000 bypasses DNS resolution entirely, preventing the OS from offering the IPv6 address.
FAQ
Why does macOS prioritize IPv6 over IPv4 for localhost?
Modern operating systems prioritize IPv6 by default to encourage the global transition away from the exhausted IPv4 address space, as defined in internet engineering standards like RFC 6724.
What is the difference between 127.0.0.1 and 0.0.0.0?
127.0.0.1 is a loopback address representing only the local machine, whereas 0.0.0.0 is a wildcard address that tells a server to listen on all available network interfaces, including local loopbacks and external IPs.
Does the Happy Eyeballs fallback add latency to my local API requests?
No noticeable latency is added; if the IPv6 connection is immediately refused, the fallback to IPv4 happens instantly. The 250ms delay only acts as a timeout if the IPv6 address is reachable but completely non-responsive.



