Guide
What is localhost? 127.0.0.1, ::1 and why your dev server lives there
September 14, 20263 min read
localhost is the first URL most developers ever type, and one of the least examined. It is worth five minutes, because two of the most annoying local networking problems come straight from misunderstanding it.
localhost is a name, and it means “me”
It is a hostname, like example.com, except it resolves to your own machine. Traffic sent to it never reaches your network card, never touches your router, and never leaves the computer. The kernel loops it straight back.
That is why the interface is called the loopback interface. On macOS it is lo0, and you can look at it:
ifconfig lo0
# inet 127.0.0.1 netmask 0xff000000
# inet6 ::1 prefixlen 128
Two addresses, because there are two internet protocols. 127.0.0.1 is loopback in IPv4, ::1 is loopback in IPv6. localhost normally resolves to both, and which one gets used depends on the program.
The IPv4 versus IPv6 gotcha
This is the one that costs people an afternoon.
Your server binds to 127.0.0.1. Your client, or your browser, resolves localhost and gets ::1 first. The connection is refused, and you are staring at a server you can see running in another terminal.
If a local connection is being refused and you are certain the process is up, try the address instead of the name:
curl http://127.0.0.1:3000
curl http://[::1]:3000
If one works and the other does not, you have found it. Bind the server to both, or point the client at the address that works.
The same split explains something you might have noticed in port listings. A single process often shows up twice, once for IPv4 and once for IPv6. It is one server, listening on both stacks, not two servers.
The whole 127.0.0.0/8 range is loopback
Not just 127.0.0.1. Everything from 127.0.0.1 to 127.255.255.254 loops back to your machine. 127.0.0.2 works exactly the same way.
That is occasionally useful. If you want two services on the same port without changing either one, you can bind them to different loopback addresses, and they will not collide because the exclusivity rule applies per address and port, not per port alone.
localhost versus 0.0.0.0
These look similar in a config file and behave very differently.
127.0.0.1 means “accept connections from this machine only”. Nothing outside can reach it, no matter what your firewall says.
0.0.0.0 means “accept connections on every interface I have”. That includes your Wi-Fi address, so anyone on the same network can reach it.
Most frameworks default to localhost for a reason. When you deliberately switch to 0.0.0.0, usually to test on a phone, you are exposing that service to your whole network. Fine on a home network, worth thinking twice about on café Wi-Fi.
This distinction matters for containers too. A server inside Docker that binds to 127.0.0.1 is reachable only from inside that container, which is why the standard advice for containerised apps is to bind 0.0.0.0 and let Docker’s published port handle the boundary.
Where /etc/hosts fits
The name has to resolve somehow, and on macOS it comes from /etc/hosts:
grep localhost /etc/hosts
You will see entries mapping localhost to 127.0.0.1 and to ::1. This is also where people add custom names like myapp.local, pointing them at loopback so a project can have a friendlier address than a port number.
If localhost ever stops resolving at all, which is rare, this file is the first place to look.
When it still will not connect
If you have the address right and the port right and it still refuses, the usual causes are, in order: the process died without you noticing, it bound to a different port than you think, or it bound to 127.0.0.1 while you are connecting over ::1.
Checking what is actually listening settles all three:
lsof -i -P -n -sTCP:LISTEN
That prints every listening socket with its address and port, so you can see exactly what your machine is offering rather than guessing.
Bosun shows the same thing continuously in the menu bar, collapsing the IPv4 and IPv6 duplicates for the same process into one row. 14 day trial, no signup, macOS 14 or later.
See this instead of typing it
Bosun lives in your menu bar and shows every open port on your Mac, live, mapped to the process behind it. One-click kill, SIGTERM first. Useful the first time this happens. Genuinely useful the fifth time it happens in one afternoon.