`lsof -iTCP -sTCP:LISTEN` Is the Command You Should Run When a Port Is Busy and You Need the Real Process, Not Another Round of Guesswork
A practical guide to `lsof -iTCP -sTCP:LISTEN` for finding which local process is actually listening on a port before you kill the wrong app or restart services blindly.
Why this command matters: “address already in use” is one of those errors that wastes time mostly because people keep solving it indirectly.
When a local server will not start because a port is occupied, or when you are not even sure which app is exposing a service on your machine, lsof -iTCP -sTCP:LISTEN gives you the concrete answer. Instead of guessing whether the culprit is Node, Python, Docker, a stale dev tool, or some background daemon you forgot existed, you ask the operating system which process is actually listening.
That is the difference between fast debugging and ritual debugging. Port conflicts are rarely intellectually hard. They become annoying because developers keep taking action before collecting the one fact that makes the next step obvious.
The command
lsof -iTCP -sTCP:LISTENThat lists processes with TCP sockets in the LISTEN state. If you want to narrow to a specific port, use:
lsof -iTCP:3000 -sTCP:LISTENTypical output includes the process name, PID, user, and the local address. That is usually enough to answer the real question: what owns this port right now?
Why this command solves the right problem
A lot of developers jump directly to:
kill -9 $(lsof -ti :3000)Sometimes that is fine. Often it is lazy.
You should first inspect the process, because the port might belong to:
- the app you actually meant to keep running
- another branch or repo you forgot was still active
- a reverse proxy like Nginx or Caddy
- a database, Docker Desktop helper, or SSH tunnel
- an auto-restarting dev tool that will come back immediately
The point of lsof is not just to kill faster. It is to understand what is occupying the socket before you decide whether to stop it, reconfigure your app, or choose a different port.
Practical workflow
If a frontend dev server refuses to bind to port 3000, do this:
lsof -iTCP:3000 -sTCP:LISTEN
ps -p <PID> -o pid,ppid,commandThat second command helps if the first output is abbreviated or the parent process matters.
If the process is safe to stop:
kill <PID>If it ignores a normal signal and you have confirmed it is the correct target:
kill -9 <PID>Use SIGKILL only after you understand what you are terminating. If the listener is managed by something else, killing it may just cause an immediate restart and teach you nothing.
Common mistakes
The first mistake is checking only “what app do I think is using this port?” instead of “what process is actually listening?”
The second mistake is confusing established connections with a listening socket. That is why the -sTCP:LISTEN filter matters. Without it, output can include network activity that is unrelated to the bind failure you are trying to solve.
The third mistake is assuming this is only for local web servers. It is equally useful when debugging:
- SSH tunnels
- local Postgres or Redis collisions
- Docker-published ports
- background sync tools
- test runners that leave watchers behind
Final recommendation
If a port conflict is blocking your work, run lsof -iTCP -sTCP:LISTEN before you touch anything else. It is still one of the cleanest ways to replace guesswork with a real process name, a real PID, and an action you can defend.