toolhq.io

All posts
6 min readby Jameel Haider

What is localhost? 127.0.0.1 and 0.0.0.0 explained

localhost is a hostname that points at your own machine. 127.0.0.1 is the IPv4 address it resolves to. Traffic sent there never touches a network cable or WiFi radio: the operating system loops it straight back internally, which is why the range is called the loopback interface. When you run a dev server and open http://localhost:3000, your browser and the server are talking entirely inside your computer.

That is the short answer. The details underneath explain a handful of errors every developer eventually hits.

localhost vs 127.0.0.1: any difference?

Functionally they almost always behave the same, but they are different things:

  • 127.0.0.1 is an address. Connecting to it needs no name resolution at all.
  • localhost is a name that must resolve. On virtually every system it resolves via the hosts file (/etc/hosts on Unix, C:\Windows\System32\drivers\etc\hosts on Windows) without any DNS query.
  • On modern systems localhost may resolve to the IPv6 loopback ::1 first. This is the classic cause of "works with 127.0.0.1 but not with localhost": the server bound only the IPv4 loopback, the client resolved localhost to ::1, and nothing was listening there. The reverse happens too.

If those two behave differently on your machine, the answer is almost always IPv4 vs IPv6 binding, and the fix is binding both or connecting to the explicit address.

The whole 127.0.0.0/8 block, over sixteen million addresses, is loopback. 127.0.0.2 works exactly like 127.0.0.1, which is occasionally handy for running several services on the same port with different loopback addresses. What that /8 notation means is covered in CIDR notation and subnetting, and you can explore the range in the Subnet Calculator.

What is 0.0.0.0 then?

0.0.0.0 is not a place you can visit. It is a meta address meaning "all interfaces", and it appears in two roles:

As a bind address. When a server binds 0.0.0.0, it listens on every network interface the machine has: loopback, WiFi, Ethernet, Docker's virtual interfaces, all of them. When it binds 127.0.0.1, it listens on loopback only.

This one distinction is the answer to two mirror image questions:

  • "Why can't my phone reach the dev server on my laptop?" The server bound 127.0.0.1 (or localhost), so it is reachable only from the laptop itself. Rebind to 0.0.0.0, then visit the laptop's LAN address, something like http://192.168.1.20:3000, from the phone. Most dev servers have a flag for it: --host for Vite, -H 0.0.0.0 for Next.js.
  • "Why is my database reachable from other machines when I never intended that?" It bound 0.0.0.0 by default. Bind loopback instead, or firewall the port.

In routing tables, 0.0.0.0/0 means "every destination", which is why it shows up as the default route and in VPN configs meaning "send all traffic through the tunnel".

Typing 0.0.0.0:3000 into a browser sometimes works anyway because browsers rewrite it to 127.0.0.1, but that is a courtesy, not a guarantee. Use localhost or 127.0.0.1 in URLs.

localhost and containers: the classic Docker trap

Inside a container, localhost means the container itself, not your laptop. A containerized app connecting to localhost:5432 looks for Postgres inside its own container and finds nothing there.

  • From a container to the host machine: use host.docker.internal (Docker Desktop) or the host's LAN IP.
  • Between containers on the same Docker network: use the service name, postgres:5432, which Docker's internal DNS resolves.
  • Port mappings like -p 3000:3000 are for reaching the container from the host, and a server inside the container must bind 0.0.0.0 for the mapping to work, because Docker's traffic arrives on the container's virtual interface, not its loopback.

The same rule explains VMs and WSL2: each has its own loopback, and "localhost" is always relative to whoever says it.

How do you fix "localhost refused to connect"?

Connection refused on loopback means nothing is listening where you knocked. In rough order of likelihood:

  1. The server is not running, or crashed on startup. Check its logs first.
  2. Wrong port. The server printed listening on 5174 because 5173 was taken, and you are still on 5173.
  3. IPv4 vs IPv6 mismatch, as above. Try 127.0.0.1:<port> explicitly.
  4. It bound a specific other interface, or inside a container without a published port.
  5. Something else owns the port. lsof -i :3000 (macOS/Linux) or netstat -ano | findstr :3000 (Windows) shows who.

A timeout instead of a refusal on loopback is rare and usually means firewall software is interfering.

Is localhost private? Mostly, with one caveat

Loopback traffic never leaves the machine, so nothing on your network can see it. The caveat: other software on the same machine can connect to anything listening on loopback. A service on 127.0.0.1 without authentication trusts every process on the computer, including anything malicious that got a foothold. For dev servers this is fine; for anything holding real credentials, it still deserves a password.

Loopback addresses are one of several special ranges that never route on the public internet, alongside the private LAN ranges like 192.168.0.0/16. How those fit together, and what your actual public address is, is covered in public vs private IP addresses, and the IP Lookup tool shows the public side in one click.