Free port 3000 on macOS without killing the wrong process
Identify the listening socket, trace its process and parent, stop it gracefully, then prove the port is genuinely free before restarting your service.
Your development server exits with EADDRINUSE: something already owns TCP port 3000. The fastest unsafe response is to paste a force-kill one-liner. The reliable response is to identify the listening socket, establish what launched it, ask it to stop cleanly, and verify the result.
Query the listener, not every connection involving the port
lsof -nP -iTCP:3000 -sTCP:LISTEN
-iTCP:3000 selects TCP activity for that port; -sTCP:LISTEN narrows it to listeners; -n avoids DNS lookups; and -P keeps numeric ports rather than translating them to service names. A typical result includes the command, PID, user, file descriptor, address family and listening address.
If nothing appears, retry with privileges because macOS may restrict visibility into another user’s processes:
sudo lsof -nP -iTCP:3000 -sTCP:LISTEN
An empty result can also mean the original error involved UDP, IPv6-only timing, a process that already exited, or a supervisor rapidly restarting the listener.
Confirm the identity before sending a signal
Assume the listener PID is 51432:
ps -p 51432 -o pid=,ppid=,user=,etime=,command=
lsof -a -p 51432 -d cwd
ps -p "$(ps -p 51432 -o ppid= | tr -d ' ')" -o pid=,ppid=,command=
The working directory and parent process often distinguish an old Node development server from a package manager, test runner, container runtime or launch agent. If the parent is a supervisor, killing only the child may make the port reappear immediately.
Stop through the owning tool when possible
Prefer the application’s normal control path: stop the foreground terminal with Control-C, use the package runner’s stop command, stop the relevant Compose service, or unload the correct launch agent. If no managed stop path exists, request graceful termination:
kill -TERM 51432
SIGTERM gives the process an opportunity to close sockets and flush work. Wait briefly and verify:
lsof -nP -iTCP:3000 -sTCP:LISTEN
ps -p 51432 -o pid=,state=,command=
Only if the process is confirmed stuck and the consequences are acceptable should you escalate:
kill -KILL 51432
SIGKILL cannot be handled by the process. It may leave temporary files, interrupted writes or stale external state, so it is not a convenient synonym for “stop.”
A safe shell sequence that resists stale PIDs
Between discovery and termination, a short-lived process can exit and macOS can eventually reuse its PID. Keep inspection and action visibly separate:
pid="$(lsof -nP -t -iTCP:3000 -sTCP:LISTEN)"
printf 'Listener PID: %s
' "$pid"
ps -p "$pid" -o pid=,ppid=,user=,etime=,command=
# After reviewing the output:
kill -TERM "$pid"
If multiple PIDs are returned, do not feed the multiline value blindly into a command. Inspect each listener and determine why more than one socket is present. Address reuse, IPv4/IPv6 listeners and supervisors can complicate the picture.
When port 3000 becomes occupied again
while true; do
date
lsof -nP -iTCP:3000 -sTCP:LISTEN
sleep 1
done
A recurring PID with a stable parent suggests a managed service. Investigate the parent and the relevant control system rather than repeatedly killing children. Common owners include launchd, a process manager, a container, an IDE task or a test watcher.
Prove the replacement service is the one answering
npm run dev
lsof -nP -iTCP:3000 -sTCP:LISTEN
curl -sv http://127.0.0.1:3000/health
A free port is only the midpoint. Record the new PID and command, then test an application route. If the service listens on ::1, 127.0.0.1, 0.0.0.0 or *, its reachability differs; verify the address matches the intended exposure.
| Observation | Meaning | Next move |
|---|---|---|
| No listener, but bind still fails | Race, other protocol/address family, or restricted visibility | Retry with sudo and inspect immediately during failure |
| PID returns after TERM | Supervisor or wrapper restarted it | Trace PPID and stop through owner |
| Listener is another user | Shared workstation or system service | Do not kill until ownership and impact are confirmed |
| New app starts but curl fails | Port ownership solved; app path still unhealthy | Inspect binding, logs and health route |
A PID is a locator, not an identity. Verify the command, user, parent and working directory before treating it as the process you meant to stop.
Sources: the solved Stack Overflow discussion (CC BY-SA), the macOS lsof reference, and the local macOS manual pages available through man lsof, man ps and man kill.
Primary source: Review the official reference ↗