devcrea

How to Find and Kill a Process by Port Number (Mac, Windows & Linux)

Stuck on "Error: port already in use"? Learn to find the Process ID (PID) and kill any open port on Mac, Windows, or Linux using lsof, netstat, and taskkill.

// The short answer

When a server reports EADDRINUSE, first identify the process that owns the port instead of killing unrelated tasks. On macOS or Linux use lsof -t -i:3000, then stop the returned PID; in PowerShell use Get-NetTCPConnection -LocalPort 3000 and pass its OwningProcess to Stop-Process. Use force only after confirming the process and port.

Full walkthrough, edge cases and why it works
In this guide · 6 sections
  1. 01Why Does This Keep Happening? (EADDRINUSE and TIME_WAIT)
  2. 02Clearing Stuck Ports on Mac and Linux
  3. 03Reclaiming Ports on Windows
  4. 04Edge Cases and Annoying Gotchas
  5. 05Killed the Process, but the Port is Still Blocked?
  6. 06How to Double-Check That the Port is Open

We’ve all been there: you sit down, run your dev server, and get hit right between the eyes with an EADDRINUSE error. It’s almost always a stray background process refusing to let go of the port you need, bringing whatever you were building to a dead stop. Luckily, tracking down that runaway process and reclaiming the port only takes a couple of seconds once you know the exact commands to run.

Bash

code8 lines
# Mac & Linux (one-liner)
kill -9 $(lsof -t -i:3000)

# Linux (fastest)
sudo fuser -k 3000/tcp

# Windows (PowerShell)
Stop-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess -Force

Why Does This Keep Happening? (EADDRINUSE and TIME_WAIT)

Node.js and the Classic EADDRINUSE

Node apps are notorious for crashing halfway through an operation without ever closing their sockets properly. When you try firing the server up again a second later, your operating system still treats that previous run as if it’s actively listening on the port. The process ends up floating detached in the background, meaning you’ll have to look up its Process ID (PID) yourself and forcefully kick it off.

If you’re working with Node day in and day out, staying on top of how you update Node.js can also save you from a few annoying runtime bugs that leave dead ports behind.

The Mystery of the TIME_WAIT State

Every once in a while, your terminal swears a port is tied up, but searching for an active process comes up completely empty. That usually means your OS put the connection into TIME_WAIT mode on purpose to let lingering network packets clear out safely without colliding. It typically sticks around for about 60 to 120 seconds. If that's what's going on, honestly just step away for two minutes and it will sort itself out without running any kill commands.

Clearing Stuck Ports on Mac and Linux

Finding the PID with lsof and Finishing It with kill

The classic, reliable way to tackle this is a simple two-step dance. First, ask lsof who’s currently squatting on your port:

Bash

code1 line
lsof -i :3000

Scan the second column of the output to grab the process ID:

Plaintext

code2 lines
COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    12345  alice   24u  IPv6  0x...      0t0  TCP *:3000 (LISTEN)

Now that you have the PID in hand, send it a standard polite exit signal:

Bash

code1 line
kill 12345

That sends a gentle SIGTERM, letting the app wrap things up cleanly. But if the process is completely hung and ignores you, bring out the heavy hammer with SIGKILL:

Bash

code1 line
kill -9 12345

Keep in mind that kill -9 drops the axe instantly and gives the app zero chance to clean up file handles, so save it for when the polite way fails. If you want to skip the manual lookup entirely, you can wrap both steps into a neat one-liner:

Bash

code1 line
kill -9 $(lsof -t -i:3000)

Grabbing Ports Faster with ss (Linux)

If you're on a modern Linux box, skip the aging netstat and reach for ss instead. It pulls data straight out of kernel space and returns almost instantaneously, which makes a noticeable difference on busy machines with thousands of open sockets:

Bash

code1 line
ss -lptn 'sport = :3000'

It spits out the binary name, connection state, and the PID right in a single glance, saving you from having to cross-check a separate list. Even better, Linux gives you fuser, which turns finding and terminating the process into one tiny command:

Bash

code1 line
sudo fuser -k 3000/tcp

By the way, if your terminal throws a fit while running any of these tools, double-checking your zsh: command not found: brew paths will almost certainly get things back on track.

Reclaiming Ports on Windows

The Modern Way: PowerShell

PowerShell makes this way friendlier than the clunky old CMD days, letting you pipe the port lookup straight into a kill command without manual parsing:

PowerShell

code2 lines
# One-liner: find and kill whatever is on port 3000
Get-NetTCPConnection -LocalPort 3000 | Select-Object -ExpandProperty OwningProcess | Stop-Process -Force

If you prefer to see what you're actually about to shut down before pulling the trigger, just split it up into two quick commands:

PowerShell

code5 lines
# Step 1: Check what's listening
Get-NetTCPConnection -LocalPort 3000

# Step 2: Terminate it
Stop-Process -Id <PID>

The Old-School Route: netstat and taskkill

If you're stuck on an older Windows box or working inside plain Command Prompt, the classic combo still gets the job done:

Bash

code1 line
netstat -ano | findstr :3000

Look for the row marked LISTENING and grab the PID listed all the way at the end. Then tell Windows to close it:

Bash

code1 line
taskkill /PID 6789 /F

That /F flag forces it down immediately, just like kill -9 does over in Unix land. And if some stubborn OS service has completely wedged the networking layer, doing a clean full system restart from CMD will wipe the slate clean as a last resort.

Edge Cases and Annoying Gotchas

When Docker is Secretly Holding the Port

Half the time this happens, the conflict isn't coming from a stray Node script at all - it's a background Docker container binding to the exact same host port. Take a second to see what Docker has running before you start nuking local processes:

Bash

code1 line
docker ps --format "table {{.Names}}\t{{.Ports}}"

You'll see something like this if a container is mapped to port 3000:

Plaintext

code2 lines
NAMES        PORTS
my-app       0.0.0.0:3000->3000/tcp

If that's the culprit, don't mess with system PIDs - just tell Docker to stop the container directly:

Bash

code1 line
docker stop my-app

If you run lsof and notice com.docker.backend sitting on your port, that's your smoking gun. The port is bound inside the container engine, so regular host-level kill commands won't do anything useful anyway.

Permission Errors and Zombie Services

Seeing a Permission Denied error? That just means the process was launched by another user account or root, so you'll need elevated privileges to kick it off:

Bash

code1 line
sudo kill -9 12345

Now, if a process dies and immediately pops right back up under a fresh PID, it’s being babysat by a daemon like systemd on Linux or launchd on macOS. In that scenario, killing the PID is pointless because the watcher will just revive it. You have to shut down the underlying service instead:

Bash

code5 lines
# See which binary is holding the port
lsof -i :5432 -sTCP:LISTEN

# Find the exact service unit managing it
systemctl list-units --type=service | grep postgres

Once you know the name, shut down the supervisor:

Bash

code5 lines
# On Linux
sudo systemctl stop postgresql

# On Mac (for Homebrew services)
brew services stop postgresql

If you can't figure out who keeps restarting it, watching the system logs in real time usually gives it away. Tossing the Linux tail command on your main log file (sudo tail -f /var/log/syslog) will point out the culprit instantly.

Killed the Process, but the Port is Still Blocked?

Walk through this checklist in order:

1. Is the process actually dead? Run lsof -i :PORT one more time. If it’s still showing up, run the kill again with sudo and -9.

2. Port blocked, but zero processes listed? You’re dealing with a lingering TIME_WAIT state. Step away for 60 to 120 seconds and it will free itself. There is nothing to kill.

3. Does the process instantly revive with a new PID? It’s wired up as a managed service. Shut it down via systemctl stop or brew services stop instead of attacking the PID.

4. Is Docker claiming it? Run docker ps to verify. Shut down the actual container instead of hunting host processes.

5. None of the above? You might have duplicate workers or orphaned instances running from another folder. Hunt down every trace of the runtime:

Bash

code5 lines
# Mac & Linux
ps aux | grep node

# Windows
Get-Process node

Wipe out those remaining instances, take a breath, and fire up your server fresh.

How to Double-Check That the Port is Open

Never assume the kill went through - take two seconds to verify the socket is truly clear before starting your server back up:

Bash

code8 lines
# Mac & Linux
lsof -i :3000

# Linux shortcut
ss -lptn 'sport = :3000'

# Windows PowerShell
Get-NetTCPConnection -LocalPort 3000

If your terminal returns silence with no output, you're good to go. Always start polite with a simple kill, reach for kill -9 only when things refuse to budge, and remember to halt the supervising service if the process keeps popping back up from the dead.

Quick answers

Frequently asked questions

How do I find what process is using a port?

On Mac and Linux, run `lsof -i :PORT` (e.g., `lsof -i :3000`). The output shows the process name and PID in the second column. On Linux you can also use `ss -lptn 'sport = :3000'` for faster results. On Windows, run `netstat -ano | findstr :3000` in Command Prompt or `Get-NetTCPConnection -LocalPort 3000` in PowerShell.

How to kill port 3000 in Node.js (EADDRINUSE error)?

Run `kill -9 $(lsof -t -i:3000)` on Mac/Linux to find and kill the Node process in one step. On Windows, use `Get-NetTCPConnection -LocalPort 3000 | Select-Object -ExpandProperty OwningProcess | Stop-Process -Force` in PowerShell. This is the most common fix for the EADDRINUSE error after a crash or improper shutdown.

Why does my port keep coming back after killing it?

The process is managed by a service controller like `systemd` (Linux) or `launchd` (Mac), which automatically restarts it. Killing the PID only stops the current instance. You need to stop the service itself: `sudo systemctl stop <service-name>` on Linux or `brew services stop <service-name>` on Mac.

What is the difference between kill and kill -9?

`kill PID` sends SIGTERM, which politely asks the process to shut down cleanly (saving files, closing connections). `kill -9 PID` sends SIGKILL, which forces the OS to terminate the process immediately with no cleanup. Always try `kill` first. Use `kill -9` only if the process ignores the graceful request.

How to kill a port without sudo (permission denied)?

Permission denied means the process was started by a different user or root. You need elevated privileges: prefix the command with `sudo` (e.g., `sudo kill -9 12345` or `sudo fuser -k 3000/tcp`). If you still cannot kill it, the process may be a protected system service that requires stopping through its service manager.

How do I check if a port is free after killing the process?

Run `lsof -i :PORT` on Mac/Linux. If the command returns no output, the port is free. On Linux you can also use `ss -lptn 'sport = :PORT'`. On Windows, run `Get-NetTCPConnection -LocalPort PORT` in PowerShell. An empty result confirms the port is released and safe to use.

How to kill a port in one command without finding the PID first?

On Mac and Linux: `kill -9 $(lsof -t -i:3000)` combines the search and kill into a single command. On Linux you can also use `sudo fuser -k 3000/tcp`. On Windows with PowerShell: `Get-NetTCPConnection -LocalPort 3000 | Select-Object -ExpandProperty OwningProcess | Stop-Process -Force`.

Something wrong or out of date?

Tools change fast. Tell the editor what no longer works.