DEV Community

Cover image for Did you know `next dev` binds to 0.0.0.0 by default?
Tetsuharu Fujiki
Tetsuharu Fujiki

Posted on

Did you know `next dev` binds to 0.0.0.0 by default?

A Japanese version of this is on Zenn.

A while back I wrote up what I learned about client isolation on public Wi-Fi. That led me to "OK, so what about dev servers?" — and it was messier than I expected.

"Just don't open your dev environment on someone else's Wi-Fi — tether, or use a VPN" is entirely correct. But between solo work, coworking spaces, meetup and conference venue Wi-Fi, and "I want to check the UI on a real phone on the same LAN," you end up running a local server on a shared network more often than you'd think.

And "the dev server only listens on localhost, so it's safe from the outside" — you kind of assume that, right? I did. In reality the behavior is all over the place depending on the framework, and some of the defaults are counterintuitive.

next dev listens on all interfaces by default

This one surprised me most. It's in a Next.js discussion on GitHub: next dev binds to 0.0.0.0 if you don't specify a hostname (vercel/next.js #64650). Unless you explicitly narrow it with something like -H 127.0.0.1, it's open to the whole LAN from the start.

And for backward-compatibility reasons this doesn't look likely to change (Docker-based dev flows would break if it were pinned to 127.0.0.1). So "it quietly closed itself after an update" isn't something to count on either.

Python's http.server does the same

When you want to quick-and-dirty serve some static files with python -m http.server — per the official docs this also binds to all interfaces by default. Unless you add --bind 127.0.0.1, anyone on the same network can reach it. The command you fired off for a bit of casual file sharing is often the most exposed thing you've got running.

Vite defaults to localhost, but…

Vite is relatively sane — recent versions default to localhost (127.0.0.1). But you've probably added --host or server.host: true at some point to check something on a physical phone. That opens it on 0.0.0.0, naturally.

The problem is there's precedent for this biting people. CVE-2023-34092 is a path-traversal bug where the server.fs.deny restriction could be bypassed with a double slash (//), letting you read files right at the project root like .env (GHSA-353f-5xf4-qw67). The advisory explicitly says exploitation requires the server to be "exposed to the network via --host or similar" — which is exactly "I ran --host to check on my phone and happened to be on coworking or café Wi-Fi." CVSS 7.5; DB credentials and API keys leak.

Docker is even more blatant

Docker's -p option binds to 0.0.0.0 by default (docs). That's a reasonable design given the whole point is "run it on a server and expose it," but reach for it on your dev machine out of habit and you get the same result.

The nastier part: even if you think you've closed a port with a host firewall like ufw, there are cases where Docker rewrites iptables directly and your firewall rules get bypassed. "I have a firewall, so I'm fine" doesn't always hold — that one's a real blind spot.

Check what your machine looks like right now

If you're suspicious, checking locally is fastest.

# What address is your Mac listening on?
# 127.0.0.1 only is fine; 0.0.0.0 or *:port means it's reachable externally
sudo lsof -i -P | grep LISTEN

# From another phone or PC on the same Wi-Fi, try to hit your Mac directly
# (open http://<your-mac-ip>:<port> in a browser)
Enter fullscreen mode Exit fullscreen mode

If you see *:3000 or 0.0.0.0:3000 and not just 127.0.0.1:3000, that's reachable right now by everyone on the same Wi-Fi.

What to actually do

The simplest answer is "stop using --host and -p during development, pin to localhost" — but that's not realistic. You want to check on a real phone, you want to show teammates something running, and editing Docker's publish settings every time is annoying. In practice it becomes "open it when you need it, leave it open when you forget."

So this lands on the same conclusion as the last post. Rather than a human remembering to close things every time, the moment you connect to an untrusted network, automatically block inbound access — that's the realistic version.

This pattern is actually one of the most common ways people tell me they use RoamSwitch: it passively audits open ports and reports the risk, and on an unregistered network it uses the kernel packet filter to stop access from the external LAN. Under the hood it's just automating "look with lsof, then close it."

The command to run before you work somewhere new

When in doubt, just run this and check there's no 0.0.0.0 or *: in there:

sudo lsof -i -P | grep LISTEN
Enter fullscreen mode Exit fullscreen mode

If you find a port that's exposed unintentionally, stop it in the relevant terminal, or kill <PID> the process from the lsof output.

Top comments (0)