Ports & dev servers

Is your dev server open to everyone on the Wi-Fi?

A server bound to 0.0.0.0 (shown as * in lsof) answers anyone on the same network: the office, the café, the conference hall. One command tells you which of yours are open, and each framework has a flag to keep it on 127.0.0.1.

Check what’s open right now

lsof -nP -iTCP -sTCP:LISTEN | awk 'NR==1 || $9 ~ /^\*:/'

This prints only the listeners bound to every interface. Read the NAME column:

AddressReachable from
127.0.0.1:3000, [::1]:3000, localhost:3000This Mac only
*:3000 (or 0.0.0.0:3000)Any device that can reach your Mac
192.168.1.24:3000Devices on that one network

You’ll usually see some system entries too, such as ControlCenter (AirPlay) or rapportd. Those are Apple services. Focus on node, python, ruby, java, com.docker.backend and friends.

To prove it, find your LAN address and request the server through it:

ipconfig getifaddr en0
curl -I http://$(ipconfig getifaddr en0):3000

If that returns HTTP headers, so would a stranger’s laptop on the same network. A server bound to 127.0.0.1 refuses the connection.

Why it matters

Dev servers aren’t built for the open internet. They often run with debug pages that print stack traces and environment variables, admin panels with seeded passwords, API routes with no auth, and source maps for your whole codebase. On home Wi-Fi the risk is small. On a shared network, anyone running a port scan finds :3000 in seconds.

Bind each framework to localhost

Several tools are already private by default. The table shows the default and the flag that changes it.

ToolDefaultStay localOpens it up
Vite, SvelteKit, Astrolocalhost(default)--host
Next.js next devall interfacesnext dev -H 127.0.0.1(default)
Nuxt nuxi devlocalhost(default)--host
Angular ng servelocalhost(default)--host 0.0.0.0
Express / plain Nodeall interfacesapp.listen(3000, '127.0.0.1')app.listen(3000)
Rails (development)localhost(default)-b 0.0.0.0
Django runserver127.0.0.1(default)0.0.0.0:8000
Flask flask run127.0.0.1(default)--host=0.0.0.0
python3 -m http.serverall interfaces--bind 127.0.0.1(default)
Docker -p 3000:3000all interfaces-p 127.0.0.1:3000:3000(default)

Two common traps:

  • Next.js and Express listen everywhere unless you say otherwise. For Next.js, change your script to "dev": "next dev -H 127.0.0.1".
  • --host left in package.json. Someone added it once to test on a phone and it stayed. Check "dev" scripts in package.json and server.host in vite.config.ts.

For Docker Compose, write the host IP in the ports list:

ports:
  - "127.0.0.1:5432:5432"

Turn on the macOS firewall

The application firewall blocks incoming connections per app. Open System Settings › Network › Firewall and switch it on. Then:

  • Click Options… to see which apps are allowed. Remove or block entries for node, python3 or ruby you don’t need reachable.
  • Block all incoming connections is the strongest setting for travel. It still allows basic services like DHCP and Bonjour, and your local localhost traffic is unaffected.
  • Enable stealth mode stops your Mac answering pings and probes on closed ports.

When a new binary first listens on the network, macOS asks “Do you want the application ‘node’ to accept incoming network connections?” Deny it unless you’re deliberately testing from another device. Note that the prompt is tied to the binary, so a new Node version from nvm or Homebrew can ask again.

The firewall is a safety net, not a fix. Binding to localhost means there’s nothing to block.

When you do want LAN access

Testing on a real phone is the usual reason. Do it on purpose: start with --host, check the QR code or URL, and stop the server when you’re done. On a network you don’t control, use a tunnel with access control rather than opening a port.

Seeing it live

Stat Space flags any listening server bound to 0.0.0.0 instead of 127.0.0.1 with a warning on its row in the Ports tab, and the menu bar attention dot lights up so you notice before someone else does. When you do want phone access, the row shows a QR code for the LAN URL. Warnings you’ve decided are fine can be muted per process and port. See the Ports feature for the rest.

Questions

Does the macOS firewall protect a server on 0.0.0.0?

Only if it’s turned on and you denied that app incoming connections. It’s off by default on most Macs, and many people click Allow on the first prompt for node or python without thinking about it.

Is localhost the same as 127.0.0.1?

For binding, both keep a server private to your Mac. localhost can resolve to ::1 (IPv6) or 127.0.0.1 (IPv4), and either is loopback-only.

How do I test my site on my phone safely?

Bind to your LAN address only while you test, on a network you trust, then go back to localhost. A tunnel such as Cloudflare Tunnel or ngrok with a password is safer on shared Wi-Fi.

Are databases exposed too?

Homebrew Postgres and Redis listen on localhost by default. Docker is the usual leak: -p 5432:5432 publishes the database on every interface, so use -p 127.0.0.1:5432:5432.

Get started

Know what your Mac is doing, without adding to it.

Free, no account. Pro is $9.99 once for the first 100 buyers.