EADDRINUSE: Port Already in Use — Quick Fix
EADDRINUSE means another process owns your port.
20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.
- ✓Node.js installed locally
- ✓Basic terminal command comfort
- ✓An app that calls server.listen
- EADDRINUSE means your Node process tried to listen on a port another process already owns. Only one listener per port is allowed.
- Find the squatter with lsof -i :3000 or netstat, then stop it with kill
(or taskkill on Windows). Never kill PIDs blindly. - The usual self-inflicted causes are a stale server still running, nodemon restarting into a second listen, or two apps sharing one hardcoded port.
- Make the port configurable with process.env.PORT || 3000 and handle listen errors so the next collision prints a helpful message.
Think of a network port as one numbered parking space. When your app starts, it tries to park in space 3000. EADDRINUSE is the attendant saying that space is taken. The fix is finding whose car is there: maybe your own old car you forgot about (a stale process), maybe a valet parked two cars in one spot (nodemon double-listen), or maybe two drivers were told the same number (hardcoded ports). Check the space, move the right car, and label spaces with env vars so drivers stop colliding.
You run npm start, the terminal spits out Error: listen EADDRINUSE: address already in use :::3000, and your app exits before serving a single request. No stack trace worth reading, no hint about who owns the port, just a refusal. It strikes after a crashed debug session, beside a second project that also likes port 3000, or inside Docker where the port mapping fights the app.
Beginners often reboot the machine or pick a random new port and move on. Both waste time and teach nothing, because the same collision returns the next day. The error is actually good news: your code is fine and the operating system is protecting you from two servers answering one port, which would corrupt traffic in confusing ways.
This guide shows the full fix in order. You will identify the process on your port with one command per OS, stop it safely, recognize the nodemon double-listen and stale-process patterns that cause most cases, understand what SO_REUSEADDR can and cannot do, and adopt the PORT environment convention that ends hardcoded collisions for good.
What EADDRINUSE Means: One Port, One Listener
TCP gives each IP and port pair exactly one active listener, and the kernel enforces that rule without exceptions. When your Node process calls server.listen(3000), it asks the kernel to bind port 3000. If any socket already owns that pair, the kernel refuses with the EADDRINUSE errno, and Node translates it into an error event on the server. With no error listener attached, the process throws and exits. The message names the address, so :::3000 means port 3000 on all IPv6 interfaces and 0.0.0.0:3000 the IPv4 equivalent.
The refusal protects you. Two servers answering one port would split traffic unpredictably: half your users would hit old code and half new code, with no log line explaining the split. The kernel would rather crash your newcomer loudly than corrupt your traffic silently. Read the error as the OS doing its job, then go find the squatter.
Node's http, https, and net servers all surface this identically, and Express inherits the behavior since it sits on http.Server. The pattern for handling it is attaching server.on('error') and checking err.code === 'EADDRINUSE', where you can print which port collided and suggest the fix. Without that handler, developers get a raw stack and start rebooting machines.
The snippet below is a minimal server with that handler. Run it twice in two terminals: the first binds cleanly and the second prints a helpful message instead of a stack. That two-terminal experiment is the fastest way to internalize what the error really says.
Finding the Squatter on macOS, Linux, and Windows
Diagnosis is one command per platform, and running it before killing anything separates engineers from process murderers. On macOS and Linux, lsof -i :3000 -sTCP:LISTEN lists the PID, user, and command owning the port. The ss alternative, ss -ltnp 'sport = :3000', shows the same without lsof installed. On Windows, netstat -ano | findstr :3000 gives the PID, resolved to a name with tasklist /FI "PID eq <pid>". Each takes under a second and removes all guesswork.
Read the output before acting. A node process started from your own project directory is a stale server you can stop normally. A pm2 or systemd parent means a supervisor owns the lifecycle and must be told to stop. A docker-proxy entry means a container published the port and needs docker stop. A system service on a low port like 80 means you should move your app, not kill infrastructure.
Killing has an order. SIGTERM first with kill <pid>, which lets Node close sockets and flush logs. Wait five seconds and re-run the listing. Only when the PID survives SIGTERM should you escalate to kill -9, which abandons connections mid-flight and can wedge the port in TIME_WAIT for a minute. On Windows, taskkill without /F asks politely and with /F forces.
The snippet prints the right command for the machine it runs on, handy in shared runbooks. Run it on any host and it tells the responder exactly what to type next instead of guessing across OS differences.
nodemon Double-Listens and Other Self-Inflicted Collisions
Restart tools cause a special flavor of this error where your own app collides with itself. nodemon watches files and reboots your process on each save. If the old process ignores SIGTERM because a connection hangs open or a timer never clears, it keeps the port while the new process tries to bind it. Every save then prints EADDRINUSE, and developers blame nodemon instead of their shutdown handling.
The second self-inflicted pattern is listening twice in code. Calling app.listen inside a function that runs per request, per test, or per retry binds a new server each time. The first call succeeds and every later call throws. Watch for listen inside route handlers, inside setInterval callbacks, and inside test beforeEach blocks that never close the previous server. One listen per process lifetime is the rule.
Tests deserve their own warning. Suites that boot a server per file on a hardcoded port fail when run in parallel, with half the workers crashing on EADDRINUSE. Give each test file port 0, which asks the kernel for any free port, then read back the assigned one with server.address().port. Parallel runs become collision-proof without any coordination.
The snippet shows the guard pattern: track the server instance and reuse it instead of listening again. Combined with closing servers in test teardown and honoring SIGTERM in apps, this removes the entire self-collision class.
SO_REUSEADDR: What It Solves and What It Doesn't
Every EADDRINUSE thread attracts the suggestion to set SO_REUSEADDR, and it almost never does what the suggester hopes. The flag lets a new socket bind a port stuck in TIME_WAIT after a recent close, which speeds up restarts within the minute after a crash. It does not permit two live listeners on one TCP port. With the flag on or off, a second active listen still fails identically.
Node already sets SO_REUSEADDR on TCP sockets by default, so manually setting it changes nothing for standard servers. The cases where it matters are exotic: multicast receivers sharing one port by design, or active-active failover pairs using SO_REUSEPORT on Linux, which explicitly distributes connections across processes. Neither applies to a normal Express app, and reaching for them masks the real squatter.
TIME_WAIT deserves a sentence because it confuses restarts. After a socket closes, the kernel parks the pair briefly to catch stray packets. A rapid restart during that window can fail even though no process shows in lsof. Waiting 60 seconds or letting Node's default flag handle it resolves what looks like a ghost collision. If restarts collide constantly rather than rarely, the cause is a living process, not TIME_WAIT.
The takeaway is priority order. A persistent collision means a living owner, so run lsof and stop it. A one-off collision seconds after a crash means TIME_WAIT, so wait a beat and retry. Socket flags enter the picture only when you are building load distribution on purpose, never as a fix for an occupied dev port.
The PORT Env Convention: Stop Hardcoding 3000
Hardcoded ports guarantee collisions the moment two projects share a machine. Every tutorial binds 3000, so every developer machine runs three apps fighting for it. The convention that ends this is one line: const PORT = process.env.PORT || 3000. Platforms from Heroku to Render to Docker inject PORT, and local runs fall back to the default. Two apps coexist by starting one with PORT=3001, no code change needed.
Document each project's default where developers look. A .env.example listing PORT=3000, a README line stating the override, and a startup log printing the bound port remove the archaeology. Log the port on every boot: listening on 3001 tells the developer in the next terminal exactly which space got taken, while silence forces another lsof round.
Docker adds a mapping layer worth understanding. The container binds its internal port while docker run -p 3000:3000 publishes it on the host. A collision can live at either layer: two containers publishing host port 3000, or the host's own server owning it. docker ps --filter publish=3000 names the container side instantly, which host-side tools show only as docker-proxy.
Enforce the convention in code review. Any literal inside listen() is a future collision, while process.env.PORT with a fallback works on laptops, CI, and platforms unchanged. One line of discipline per project ends an entire category of Monday-morning breakage.
listen().A Two-Minute Recovery Checklist
When the error appears, work the list in order and stop at the first step that resolves it. First, read the port from the message. Second, list the owner with lsof or netstat and note whether you recognize the process. Third, stop it through the right channel: its terminal, its supervisor, or a polite SIGTERM. Fourth, confirm the port is free by re-running the listing and seeing no output. Fifth, start your app and watch for the listening log line. Two minutes, no reboot, no random port roulette.
If the checklist fails, escalate in fixed order. Check supervisors like pm2, systemd, and Docker for respawners. Check for a second terminal or IDE run configuration booting the same app. Check TIME_WAIT by waiting sixty seconds after a crash. Only then consider that the port belongs to infrastructure and your app should move via PORT.
After recovery, spend five minutes on prevention so the checklist stays shelved. Add the env-var port line, attach the EADDRINUSE error handler, and note the project's default port in the README. Each takes a minute and pays back every future collision before it starts.
Keep the emotional framing right. This error means the operating system protected your traffic from a split-brain server. Thank the kernel, evict the squatter properly, and move on to real problems.
A Stale pm2 Process Blocked Every Deploy for 41 Minutes
- One supervisor must own every port, so manual node starts on servers need to fail the deploy preflight instead of lingering as orphans.
- A deploy that crashes on bind in seconds is a port-ownership problem, not a code problem. Check lsof before reading the diff.
- Health checks that only test HTTP answers cannot see two processes. Assert the responder's PID matches the supervised process.
| File | Command / Code | Purpose |
|---|---|---|
| port-server.js | const http = require('http'); | What EADDRINUSE Means |
| find-port-owner.js | const PORT = Number(process.argv[2]) || 3000; | Finding the Squatter on macOS, Linux, and Windows |
| listen-once.js | const http = require('http'); | nodemon Double-Listens and Other Self-Inflicted Collisions |
| reuse-check.js | const net = require('net'); | SO_REUSEADDR |
| port-env.js | const http = require('http'); | The PORT Env Convention |
Key takeaways
Common mistakes to avoid
5 patternsKilling the PID without listing the process first
Switching ports without finding the squatter
Calling listen inside handlers, retries, or test hooks
Assuming SO_REUSEADDR lets two servers share a port
Hardcoding port 3000 in every project
Interview Questions on This Topic
What does EADDRINUSE mean and which layer raises it?
Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.
That's Node.js. Mark it forged?
6 min read · try the examples if you haven't