Guide
What is EADDRINUSE? The error, the cause, and the case where nothing is listening
September 14, 20264 min read
Error: listen EADDRINUSE: address already in use :::3000
Almost every developer has read that line. It shows up in Node, but it is not a Node error. It is a system call failing, and the name is the same everywhere.
Reading the name
EADDRINUSE breaks into three parts. The E marks it as an error constant, the Unix convention. ADDR is address. IN USE is exactly what it says.
It is returned by bind(), the system call a server makes when it claims an address and port. Your program asked the kernel for port 3000, and the kernel said no, because the rule is that only one socket may listen on a given address and port at a time.
That is why the message wording differs by language while the code does not. Python raises OSError: [Errno 48] Address already in use, Go returns bind: address already in use, Node prints EADDRINUSE. Same refusal underneath.
Note the :::3000 in the Node message. Those three colons are the IPv6 wildcard address, which means the server was trying to bind every interface on IPv6.
The ordinary cause
Nine times out of ten it is a previous run of the same server that did not exit cleanly. The terminal was closed, the process was suspended and never resumed, a crash left an orphan, or a file watcher restarted something before the old one released the port.
The fix is to find it and terminate it:
lsof -i :3000
Then, once you know what it is:
lsof -ti :3000 | xargs kill
-t prints just the process IDs, and xargs passes all of them to a single kill. That last detail matters for servers running several workers, since each worker holds the same port and you want them all to go.
The other cause: nothing is listening
This is the one that makes people think something is deeply broken. You get EADDRINUSE, you run lsof -i :3000, and it prints nothing at all.
The port is in TIME_WAIT.
When a TCP connection closes, the side that closed it first keeps the port reserved for a short while, typically around 30 seconds on macOS. The reason is careful rather than arbitrary: stray packets from the connection that just ended could still be in flight, and reusing the port immediately risks delivering them to a brand new connection that has nothing to do with them.
So the socket is gone, no process owns it, and the port is still not available. lsof -i :3000 shows nothing because there is no process. To see the state:
netstat -an | grep 3000
If you see TIME_WAIT, you have found it. The honest fix is to wait half a minute.
If you would rather not wait, that is what SO_REUSEADDR is for. Most frameworks set it already, which is why you rarely hit this in day to day development and why it feels so strange when you do.
Why it happens more with some tools
Some workflows produce this more often than others.
Watch mode and hot reload restart the server as soon as a file changes. If the restart is faster than the old process releasing the port, you race yourself.
Docker publishes container ports through a docker-proxy process. A container that did not stop cleanly can leave the published port held, and the process name tells you nothing about which container it was.
Multi worker servers like gunicorn or Odoo hold one port across several processes. Killing one worker frees nothing, because the others still have it. This is exactly why the xargs form above matters.
Preventing it rather than fixing it
Stop your dev server with Ctrl+C rather than closing the terminal window. Ctrl+C sends SIGTERM and lets the process release the port; closing the window can leave it orphaned.
If a particular project keeps colliding, give it a port nobody else wants. There is a lot of room between 1024 and 49151, and we wrote about how those ranges work.
Bosun sits in the menu bar and shows what is holding each port before you hit the error, with Docker containers named properly rather than shown as docker-proxy. macOS 14 or later, 14 day trial, no account.
See this instead of typing it
Bosun lives in your menu bar and shows every open port on your Mac, live, mapped to the process behind it. One-click kill, SIGTERM first. Useful the first time this happens. Genuinely useful the fifth time it happens in one afternoon.