"Bind: Address Already in Use" on Mac, How to Fix It
The "address already in use" error means another process already holds the port. Here's what causes it on Mac, how to find and free the port, and why TIME_WAIT sometimes keeps it locked.
You start a server and it dies immediately with bind: address already in use (or Address already in use, errno 48 on macOS). The message is blunt but accurate: something already owns the port you’re trying to bind to. Here’s how to find it and get your port back.
What the error actually means
When a program wants to listen on a port, it calls bind() on that port number. If another socket is already bound there, the OS refuses with EADDRINUSE, “address already in use.” On macOS this is errno 48 (it’s 98 on Linux, in case you’re cross-referencing).
There are two distinct causes, and the fix differs:
- Another process is genuinely holding the port (the common case).
- A previous instance of your own program left the port in
TIME_WAIT(the sneaky case).
Step 1: Find what’s on the port
Replace 3000 with your port:
sudo lsof -i :3000 -n -P
If something owns it, you’ll see the process and PID:
COMMAND PID USER ... NODE NAME
node 1421 aaron ... TCP *:3000 (LISTEN)
Now you know node, PID 1421, is holding port 3000.
Step 2: Free the port
If that process is safe to stop (an old dev server, a forgotten script), kill it:
# Graceful first, lets it clean up
kill 1421
# If it won't go, force it
kill -9 1421
Or do it in one shot without copying the PID:
kill -9 $(lsof -ti :3000)
The -t flag makes lsof print just the PID, which feeds straight into kill. See kill a process by port on Mac for when to prefer the graceful SIGTERM over the forceful SIGKILL.
The TIME_WAIT case: nothing is on the port, but it’s still “in use”
Sometimes lsof -i :3000 returns nothing, yet you still get “address already in use.” This is usually TIME_WAIT: when a TCP connection closes, the OS keeps the socket reserved for a short cooldown (typically a minute or two) to make sure no stray packets from the old connection get misdelivered to a new one.
You can confirm it:
netstat -an | grep 3000
If you see the port in TIME_WAIT rather than LISTEN, that’s what’s blocking the rebind. Two ways through it:
- Just wait. The state clears on its own, usually under two minutes.
- Make your server reuse the address. Most servers can set the
SO_REUSEADDRsocket option, which lets a new socket bind to a port still inTIME_WAIT. In Node it’s on by default; in many frameworks there’s a “reuse address” flag. This is the right long-term fix for servers you restart constantly.
Step 3: Or just change your port
If you can’t safely kill what’s holding the port, point your app somewhere else instead:
# Examples
PORT=3001 npm start
flask run --port 5001
Moving your app is often safer than killing a process you can’t immediately identify, especially if the port belongs to a system service.
Why this keeps happening
The root cause is always the same: only one program can listen on a port at a time. Crashed servers that didn’t release their port, two tools both defaulting to 3000, a debugger still attached in the background, all produce the same error. The skill is quickly identifying what holds the port, which is exactly what the steps above do.
Find the culprit instantly
Portie shows every process holding a port on your Mac in one live table, so when you hit “address already in use,” you can see exactly what’s on that port without typing an lsof command, and end it with one click.
Local monitoring is free. The $8.99 one-time unlock adds one-click process termination (graceful or forced) and remote port scanning. Download Portie and never decode errno 48 by hand again.