EADDRINUSE: address already in use — free the port
Hi, it's BlueByte. EADDRINUSE just means something already has the port your app wants — usually a previous run of the app itself that never shut down. Let's find exactly what's holding it, decide whether to stop it or move your app, and cover the case where the port looks free but still won't bind.
What the error means
A server fails to start:
Error: listen EADDRINUSE: address already in use :::3000The port is taken. Another process — often a previous run of the same app that did not shut down — already holds it, so the new process cannot bind. The fix depends on whether the holder is a stale instance you can kill or a service you actually need, so the first job is to identify it.
Why the port is already taken
Only one process can listen on a given TCP port per interface. You hit EADDRINUSE when:
- An earlier instance is still running — the most common case, especially after a crash or a terminal you closed without stopping the server.
- A different service legitimately uses the same port.
- A crashed process left its socket open, so the port is held until it is released.
- On fast restarts, the previous socket is in
TIME_WAITand the port is not yet free to rebind.
Find exactly what's holding it
Don't kill blindly — find the holder first. On Linux:
ss -ltnp | grep :3000On macOS:
lsof -nP -iTCP:3000 -sTCP:LISTENBoth print the PID and the program name. If the name is your own app from a previous run, it is a stale instance; if it is something else you rely on, do not kill it — move your app instead. If nothing is listed as LISTEN but you still cannot bind on a fast restart, the port is in TIME_WAIT — ss -tan | grep :3000 will show it.
Stop the holder, or move your app
- If it is the stale instance, stop it:
kill 12345 # graceful
kill -9 12345 # only if it ignores the first- If the holder is a service you need, run your app on a different port:
PORT=3001 npm startA real case: a detached dev server
You stop a dev server with Ctrl+C, edit a file, and restart — and it fails with EADDRINUSE :::3000. ss -ltnp | grep :3000 shows a node process from the previous run still listening; closing the terminal earlier had detached it, not stopped it. You kill that PID, confirm ss no longer lists it, and start the server, which binds cleanly. Because this keeps happening on quick restarts, you switch the dev server to reuse the address so a lingering TIME_WAIT socket no longer blocks the rebind.
Confirm the port is yours
Start the server again and confirm it binds:
ss -ltnp | grep :3000 # now shows only your new processIf it starts cleanly and the listener is your app, the port is yours.
Keep it from recurring
Shut servers down cleanly (Ctrl+C, not closing the window), and if a supervisor like systemd, pm2, or Docker keeps respawning the old process, stop it there rather than killing the PID by hand. For fast dev restarts, enable address reuse so the socket does not linger.
How this differs from ECONNREFUSED and EACCES
EADDRINUSE is a local conflict — the port on your own machine is taken. ECONNREFUSED is the opposite end: you tried to connect and nothing was listening. And EACCES on a low port (under 1024) is a permission problem, not a conflict — that port needs elevated privileges. When you hit EADDRINUSE, identify the holder with ss/lsof before you kill anything.
Related questions
kill did not free the port. What now?
The process ignored the signal or respawned. Confirm the PID with ss/lsof again and use kill -9 as a last resort. If it respawns, a supervisor (systemd, pm2, docker) is restarting it — stop it there.
The port is free but I still get EADDRINUSE on restart.
The old socket is in TIME_WAIT. Wait a minute, or run the server with address reuse (SO_REUSEADDR) enabled so it can rebind right away.
Why does :::3000 have three colons?
That is the IPv6 wildcard address for port 3000. The server is listening on all IPv6 interfaces; the port conflict is the same regardless of IPv4/IPv6.
I get EADDRINUSE only on ports under 1024.
That may actually be EACCES (permission) rather than a conflict — ports below 1024 require elevated privileges. Use a higher port or grant the capability.
A Docker container reports the port in use.
A previous container may still publish it. Run docker ps -a --filter publish=3000, remove the old container, or change the published host port.
References
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
systemd: Start request repeated too quickly
systemd refuses to start a unit that was started more than StartLimitBurst times (default 5) within StartLimitIntervalSec (default 10s), and Restart= counts against that limit. With the 100 ms default RestartSec a crashing service burns all five attempts in under a second. Find the real crash in the journal, fix it, run reset-failed, and give restarts room with RestartSec.
SSH: Received disconnect ... Too many authentication failures
Your agent is offering more keys than the server will let you try. Every public key sshd looks at burns one of the MaxAuthTries attempts — six by default, often three on a hardened host — so the right key never gets its turn and the server hangs up before you type anything. IdentitiesOnly=yes with an explicit IdentityFile pins the connection to one key and the attempt count drops to one.
Active Directory: replication fails with error 1722, The RPC server is unavailable
RPC reports 1722 (0x6ba, RPC_S_SERVER_UNAVAILABLE) when a lower layer fails to connect, so the real fault is almost never RPC itself — it is DNS, a blocked port, or a host-side setting on one of the two domain controllers. repadmin tells you which partner is failing, dcdiag /test:dns rules out name resolution, and Test-NetConnection plus the dynamic port range settle the firewall question. The most common miss is a rule that allows TCP 135 but not 49152–65535.
Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license
The 120-day RD Licensing grace period ended and the session host has no usable license server, so it refuses sessions. GetGracePeriodDays returning DaysLeft 0 and an empty SpecifiedLSList confirm it in seconds. The fix is a real, activated license server with CALs that are new enough for the host — a 2019 CAL cannot serve a 2022 session host — configured through the deployment or the Licensing policies, plus RPC ports open between the two.
Windows 11: "Your organization's security policies block unauthenticated guest access" when opening a NAS share (0x80070035)
The SMB client on Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro and Windows Server 2019+ refuses guest logons by default, and Windows 11 24H2 Enterprise/Pro/Education also requires SMB signing, which guest sessions can't do. A NAS share that only offers guest access therefore fails with the 'block unauthenticated guest access' dialog, Error code 0x80070035, or System error 3227320323, and Event ID 31017 'Rejected an insecure guest logon' lands in the SmbClient/Security log. The fix Microsoft recommends is a real account on the NAS and signing support in its firmware; Set-SmbClientConfiguration -EnableInsecureGuestLogons $true (plus -RequireSecuritySignature $false on 24H2) is the escape hatch, and it costs you signing and encryption on that client.
Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — who holds it and how to wait for it
Another package manager, usually Ubuntu's unattended-upgrades fired by a persistent systemd timer at boot, holds the dpkg frontend lock while your apt-get runs. apt-get gives up at once while apt waits because Ubuntu ships binary::apt::DPkg::Lock::Timeout "120" for the apt binary only. Read the PID from the message, let the run finish or pass -o DPkg::Lock::Timeout=<seconds> to apt-get, run dpkg --configure -a only after a genuinely interrupted run, and never delete the lock file: it is an fcntl lock the kernel releases when the holder exits.