Kill port: free up a busy port (3000, 8080)

Kill port: find and kill the process using port 3000 or 8080 on macOS, Linux, Windows, Docker and Kubernetes. Tested commands.

Laptop screen showing a command line in green above a backlit keyboard

Short answer: to kill the process using a port, first identify its PID, then stop it. On macOS and Linux: lsof -ti :3000 | xargs kill (and kill -9 only if the process resists). On Linux with psmisc installed, fuser -k 3000/tcp does both in one command. On Windows: netstat -ano | findstr :3000 then taskkill /PID <pid> /F. Inside a Docker container, what you need is not a kill but docker stop.

The message is always the same: Error: listen EADDRINUSE: address already in use :::3000 when launching next dev, or bind: address already in use on a kubectl port-forward. A process is still holding the port — often a development server started in a terminal tab closed too quickly, sometimes a forgotten container, sometimes an SSH tunnel. Here are the commands we use daily, the differences between systems, and the three cases where killing the process is a bad idea.

Laptop screen showing a command line in green above a backlit keyboard

How do you kill the process using a port?

In two steps: find the PID listening on the port, then send a stop signal to that PID. The command depends on your system.

SystemIdentifyKill
macOSlsof -i :3000kill $(lsof -ti :3000)
Linux (modern)ss -ltnp 'sport = :3000'kill <pid>
Linux (psmisc)fuser 3000/tcpfuser -k 3000/tcp
Windows (cmd)`netstat -ano \findstr :3000`taskkill /PID <pid> /F
Windows (PowerShell)Get-NetTCPConnection -LocalPort 3000Stop-Process -Id <pid>
Dockerdocker ps --filter publish=3000docker stop <name>

The detail that matters: lsof -t outputs only PIDs, which makes it directly composable with kill. On macOS, lsof -ti :3000 is the shortest form that works without sudo for your own processes. lsof is documented in the Apple tools manual pages and ships by default on macOS; on Linux it is no longer always installed, hence the value of knowing ss, provided by iproute2.

The Linux version with nothing to install

ss is part of the base package of most distributions since net-tools was abandoned:

# Who is listening on 3000? (-l listen, -t tcp, -n numeric, -p process)
sudo ss -ltnp 'sport = :3000'
# LISTEN 0 511 *:3000 *:* users:(("next-server",pid=48213,fd=23))

kill 48213

Without sudo, the users: column stays empty for processes that do not belong to you: you see the port is busy but not the culprit. This is the number one cause of "my command finds nothing even though the port is taken".

The alias worth keeping

We systematically put this function in the dotfiles of dev machines, because it avoids copy-pasting a PID:

# ~/.zshrc or ~/.bashrc
killport() {
  local pid
  pid=$(lsof -ti ":$1" -sTCP:LISTEN)
  if [ -z "$pid" ]; then
    echo "No process listening on port $1"
    return 1
  fi
  echo "Stopping PID $pid on port $1"
  kill "$pid" || kill -9 "$pid"
}

The -sTCP:LISTEN matters: without it, lsof -ti :3000 also returns outbound connections to port 3000 on another machine. You would then kill your browser or your HTTP client instead of the server.

Why not just run kill -9?

Because kill -9 sends SIGKILL, which the process cannot intercept: no closing of connections, no flushing of buffers, no cleanup of temporary files. On a Next.js development server, that is harmless. In three other cases, it is not.

  • A database listening on 5432 or 3306: a SIGKILL forces a recovery at restart. PostgreSQL handles it (it is the equivalent of a power cut), but the clean shutdown is still pg_ctl stop or systemctl stop postgresql. The PostgreSQL documentation on shutting down the server details the smart, fast and immediate modes; SIGKILL is worse than immediate.
  • A process with children: kill on the parent can leave orphaned children still holding the port (a classic with npm run dev spawning a subprocess). Kill the group: kill -- -$(ps -o pgid= <pid> | tr -d ' ').
  • A container: the PID seen from the host is that of the process inside the container. Killing it leaves Docker in an inconsistent state; docker stop sends SIGTERM then SIGKILL after the grace period (10 seconds by default).

So the correct order is always: SIGTERM (kill), wait two seconds, and SIGKILL (kill -9) only if the port is still busy.

The port is free but the bind still fails

Three causes, all common, and none of them is fixed with a kill.

1. TIME_WAIT state. After a socket is closed, the kernel keeps the quadruple in TIME_WAIT for roughly 60 seconds (2×MSL on Linux). A server that does not set the SO_REUSEADDR option will refuse to restart during that window. Check with ss -tan state time-wait '( sport = :3000 )'. The only correct answer is to wait, or to fix the server — not to touch net.ipv4.tcp_tw_reuse.

2. Port reserved by Windows. Hyper-V, WSL2 and the Windows NAT Service reserve ranges of dynamic ports at startup, and port 3000 or 5000 can fall inside them. netstat then shows no process at all. List the exclusions:

netsh interface ipv4 show excludedportrange protocol=tcp

If your port is in an excluded range, switch ports or reserve it explicitly with netsh int ipv4 add excludedportrange. This is the typical symptom of "it worked yesterday" after a Windows restart.

3. Permissions on ports below 1024. On Linux, binding to 80 or 443 requires CAP_NET_BIND_SERVICE or root. The error is EACCES, not EADDRINUSE — read the error code before hunting for a process.

Glasses placed in front of a computer screen showing code and a development editor

What about Docker, Kubernetes or CI?

The kill reflex does not apply: in these environments the port is held by an object managed by an orchestrator, and it is the orchestrator you have to ask.

  1. A Docker container publishing the port. docker ps --filter publish=3000 gives you the container; docker stop <name> stops it cleanly. If docker ps shows nothing, look for stopped containers whose port is still mapped by the proxy: docker ps -a, then restart the daemon as a last resort.
  2. A ghost kubectl port-forward. A port-forward launched in the background survives the terminal being closed. lsof -ti :8080 -sTCP:LISTEN returns a kubectl process: that is the one to kill, not a pod. The Kubernetes documentation's best practices for accessing services remind you that port-forward is a debugging tool, not an exposure mechanism.
  3. Port already taken inside a pod. Two containers in the same pod share the network namespace: two containerPort: 3000 entries in the same pod give the second one an EADDRINUSE. The diagnosis happens in the container logs, not on the host.
  4. GitLab CI runner. On a shared runner, a previous job may have left a service listening. Rather than killing blindly, have your jobs listen on a dynamic port (PORT=0 when the server supports it) or use GitLab CI services, which get their own network. It is the same reasoning as in our GitLab CI/CD pipeline for Next.js on Kubernetes: a job that depends on the runner's state is not reproducible.

How do you avoid the problem instead of suffering it?

By making the port non-conflicting by design. Four habits that remove 90% of occurrences:

  • One port per project, written in the repository's .env and not in developers' heads: 3001 for app A, 3002 for app B. Next.js accepts next dev -p 3001 or PORT=3001.
  • Stop with Ctrl+C, never by closing the tab. Closing the terminal window does not always send SIGHUP to the process group, depending on the emulator and the shell; it is the most mundane origin of orphaned servers.
  • docker compose down instead of Ctrl+C on a compose up: the former also removes the network and the port mappings.
  • A predev script that frees the port before starting, for team projects:
{
  "scripts": {
    "predev": "lsof -ti :3000 -sTCP:LISTEN | xargs -r kill || true",
    "dev": "next dev"
  }
}

The -r flag of xargs (GNU) avoids calling kill with no argument when the port is free; on macOS, use xargs without -r together with the || true.

Computer screen showing several terminals with system commands, processes and error messages

The special case of a busy port in production

In production, a busy port is not a dev-machine annoyance: it is a blocked deployment. The classic scenario is a rolling update where the old pod has not yet released its port while the new one is starting. On Kubernetes, with hostPort or hostNetwork: true, the conflict is guaranteed: the port is taken on the node, not in the pod. The answer is not to kill a process but to remove hostPort and go through a Service plus an ingress or a Gateway — the mechanism described in our article on the Gateway API as a replacement for ingress-nginx.

On a single-server VM, the same conflict appears between two versions started by systemd. The correct order is ExecStop then ExecStart, with a TimeoutStopSec greater than the connection-closing delay of your HTTP server (Node.js does not close keep-alives before server.close() plus the completion of in-flight requests).

Frequently asked questions

What is the shortest "kill port" command?

On macOS and Linux with lsof installed: kill $(lsof -ti :3000 -sTCP:LISTEN). On Linux with psmisc: fuser -k 3000/tcp. On Windows: taskkill /PID <pid> /F after reading the PID from netstat -ano | findstr :3000. There is no native killport command on these systems; it is always a combination of two tools.

Why does lsof -i :3000 return nothing even though the port is busy?

Three reasons, in order of frequency: you are not root and the process belongs to another user (rerun with sudo); the port is held by a container whose mapping goes through the Docker proxy (look with docker ps); the port is in a range reserved by Windows/WSL2 (netsh interface ipv4 show excludedportrange).

Do you need a reboot to free a port?

No, except in pathological cases. A port stays blocked either by a process (which can be killed), or by a TIME_WAIT state (which expires on its own in about a minute), or by a system reservation (changeable without a reboot on Windows with netsh). Rebooting the machine hides the cause, and it comes back at the next launch.

How can you find out what the process is doing before killing it?

ps -p <pid> -o pid,ppid,user,etime,command gives the full command line, the user and the run time. An etime of several days on a dev machine almost always signals an orphaned server; a process started thirty seconds ago may well be your colleague pair programming on a shared session.

In summary

  • Identify before killing: lsof -ti :<port> -sTCP:LISTEN (macOS/Linux), ss -ltnp (Linux), netstat -ano | findstr (Windows).
  • SIGTERM first (kill), SIGKILL (kill -9) only as a second resort — and never first on a database.
  • The -sTCP:LISTEN filter prevents you from killing an outbound connection instead of the server.
  • In Docker, Kubernetes or CI, you do not use kill: docker stop, removing the port-forward, or a dynamic port in the job.
  • Port free but bind refused: think TIME_WAIT, Windows/WSL2 reserved ranges, or permissions on ports below 1024.
  • Prevention: one port per project in the .env, stopping with Ctrl+C, docker compose down, a predev script.

If these port conflicts also happen in your staging environments or your deployments, it is usually the sign of a hand-built network exposure. We take over this kind of architecture in our infrastructure and operations engagements, and we industrialise development environments on the application development side. To discuss a concrete case, the contact page is the shortest path.