connect ECONNREFUSED ::1: why “localhost” fails when 127.0.0.1 works

localhost is a name with two addresses: 127.0.0.1 (IPv4) and ::1 (IPv6). If the client picks one and the server only listens on the other, the connection is refused even though the server is running.

What you see

Error: connect ECONNREFUSED ::1:5432          # Node.js 17+
Error: connect ECONNREFUSED ::1:3000          # fetch / axios to a local API
socket.error: [Errno 97] Address family not supported by protocol   # Python, IPv6 off
bind: Cannot assign requested address        # binding ::1 where IPv6 is disabled

The ::1 in the message is the giveaway: the client went to IPv6 loopback. Look up the server’s actual listening address:

ss -ltnp | grep 5432          # Linux
lsof -iTCP:5432 -sTCP:LISTEN  # macOS
# 127.0.0.1:5432  -> IPv4 only
# [::1]:5432      -> IPv6 only
# 0.0.0.0:5432 / *:5432 / [::]:5432 -> all addresses

Why it started happening

Fixes, from quickest to best

  1. Use the literal address in the client: postgres://[email protected]:5432/db, http://127.0.0.1:3000. Unambiguous and instant.
  2. Prefer IPv4 in Node app-wide: node --dns-result-order=ipv4first app.js or dns.setDefaultResultOrder('ipv4first') at startup.
  3. Make the server listen on both. Many servers accept a list or :: (dual-stack on Linux): PostgreSQL listen_addresses = 'localhost' covers both families; Node server.listen(3000) with no host listens on :: (and IPv4 too); Vite server.host, Python http.server --bind ::. Only listen beyond loopback if you really mean to: 0.0.0.0 and :: expose the port to the network unless a firewall stops it.
  4. Check /etc/hosts (Windows: C:\Windows\System32\drivers\etc\hosts). It should contain both 127.0.0.1 localhost and ::1 localhost; a missing or edited line changes which one wins.

Docker and containers

Inside a container, localhost means the container itself, not your machine, so a database on the host is never at localhost from inside. Use the service name in Docker Compose (db:5432) or host.docker.internal. Port publishing like -p 127.0.0.1:5432:5432 binds IPv4 only on the host, so ::1 clients on the host are refused. Refused even with the right address and port? Work through localhost refused to connect and EADDRINUSE.

Reading the addresses

::1 is IPv6 shorthand for 127 zero bits followed by a 1; :: means “every address”. In URLs an IPv6 address needs square brackets: http://[::1]:3000/ (percent-encoding doesn’t replace them; see the URL encoder). 127.0.0.1 is the first address of the whole 127.0.0.0/8 loopback block; you can see it as one 32-bit number (2130706433) in the number base converter. Remote host timing out on port 22? See ssh Connection timed out port 22.

Related

TypeError: fetch failed (Node) · ssh Connection refused port 22 · ETIMEDOUT / connect ETIMEDOUT · getaddrinfo ENOTFOUND · ECONNRESET

Questions

Why does localhost give ECONNREFUSED ::1 but 127.0.0.1 works?

localhost resolves to both ::1 (IPv6) and 127.0.0.1 (IPv4). Your client tried ::1 first, but the server only listens on 127.0.0.1. Connect to 127.0.0.1 directly or make the server listen on both.

Why did this start after upgrading Node.js?

Since Node 17, Node keeps the operating system's address order instead of putting IPv4 first, and most systems list ::1 first for localhost. Use --dns-result-order=ipv4first or connect to 127.0.0.1.

Is ::1 the same as localhost?

Yes. ::1 is the IPv6 loopback address, just as 127.0.0.1 is the IPv4 one. Both mean this machine, but a server listening on one family does not automatically answer on the other.

Should I listen on 0.0.0.0 to fix it?

Only if other machines should reach the service. 0.0.0.0 and :: accept connections on every network interface. For local development, listen on localhost or both loopback addresses instead.

Published .

Try it

Related guides

Newest guides