Home › JavaScript › EADDRINUSE: Port Already in Use — Quick Fix
Beginner 6 min · September 23, 2026

EADDRINUSE: Port Already in Use — Quick Fix

EADDRINUSE means another process owns your port.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.

Follow
✓ Production
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 9 min
  • ✓Node.js installed locally
  • ✓Basic terminal command comfort
  • ✓An app that calls server.listen
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • 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.
✦ Definition~90s read
What is EADDRINUSE Port Already in Use Fix?

EADDRINUSE is an operating system error that Node.js surfaces when server.listen() requests a port and address combination already owned by another socket. The name reads as error, address in use. TCP enforces one active listener per IP and port pair, so the kernel rejects the second bind and Node emits an error event on the server object.

★
Think of a network port as one numbered parking space.

Uncaught, that event crashes the process with the familiar listen EADDRINUSE message naming the port, such as :::3000 for all IPv6 interfaces.

The squatter is usually one of four things. A stale instance of your own app still running from an earlier terminal, a crashed debug session, or a background pm2 process. A second project on the same machine that hardcodes the same port, extremely common when every tutorial defaults to 3000.

A restart tool like nodemon that boots a new process before the old one releases the port. Or a system service, proxy, or Docker mapping already bound there. Each needs a different response, which is why identifying the PID before killing anything matters.

Two misconceptions slow people down. First, SO_REUSEADDR does not let two servers share a port for normal traffic. It permits rapid rebinding during restart windows and multicast sharing, but a second listen on an owned TCP port still fails. Node already sets this flag by default. Second, switching ports without checking leaves the stale process alive, leaking memory. Always find the owner first.

The durable fix has three layers. Diagnose with lsof or ss to name the PID. Resolve by stopping the right process or choosing a genuinely free port. Prevent with the PORT environment convention plus a listen-error handler that prints actionable guidance instead of a raw stack. The sections below walk each layer with commands you can copy.

Plain-English First

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.

port-server.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const http = require('http');
const PORT = Number(process.env.PORT) || 3000;

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify({ ok: true, port: PORT }));
});

server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.error('port ' + PORT + ' is taken. Run: lsof -i :' + PORT);
    process.exit(1);
  }
  throw err;
});

server.listen(PORT, () => console.log('listening on ' + PORT));
Try it live
📊 Production Insight
Crashes that happen within seconds of start, before any request logging, are almost always bind failures or missing env vars rather than logic bugs. Teach on-call to read the first error line literally: EADDRINUSE means stop reading the diff and start listing processes.
🎯 Key Takeaway
The kernel allows one listener per IP and port, so the second bind fails fast. Attach a server error handler that names the port and the diagnostic command.

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.

find-port-owner.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
const PORT = Number(process.argv[2]) || 3000;

if (process.platform === 'win32') {
  console.log('run: netstat -ano | findstr :' + PORT);
  console.log('then: tasklist /FI "PID eq <pid>"');
  console.log('stop: taskkill /PID <pid> /F');
} else {
  console.log('run: lsof -i :' + PORT + ' -sTCP:LISTEN');
  console.log('stop politely: kill <pid>');
  console.log('force only if needed: kill -9 <pid>');
}
Try it live
⚠ Never Kill PIDs Blindly
A PID on your port can belong to a database, a teammate's debug session, or production traffic. Name the process first with lsof or tasklist, confirm it is safe to stop, and only then signal it.
📊 Production Insight
Shared staging hosts accumulate the most orphans because five developers start servers and close laptops without stopping them. A weekly cron that reports listening PIDs older than 24 hours on dev ports prevents the Monday-morning collision ritual.
🎯 Key Takeaway
List the port owner with lsof, ss, or netstat before signaling anything. SIGTERM first, SIGKILL only when the process survives, and supervisor-owned processes stop through their supervisor.

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.

listen-once.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const http = require('http');
let server = null;

function start(port) {
  if (server) {
    console.log('reusing existing server on ' + port);
    return server;
  }
  server = http.createServer((req, res) => res.end('ok'));
  server.listen(port, () => console.log('listening on ' + port));
  return server;
}

function stop(done) {
  if (!server) return done && done();
  server.close(() => { server = null; done && done(); });
}

start(3000);
start(3000);
stop(() => console.log('closed cleanly'));
Try it live
📊 Production Insight
Hanging keep-alive connections are the usual reason old processes survive restarts. Set server.keepAliveTimeout and headersTimeout explicitly, and close idle connections on SIGTERM, or every rolling restart becomes a race between the old process releasing the port and the new one binding it.
🎯 Key Takeaway
Listen exactly once per process and close servers in teardown. Use port 0 in parallel tests so the kernel assigns a free port instead of fighting over a hardcoded one.

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.

reuse-check.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const net = require('net');

function tryBind(port) {
  const s = net.createServer();
  s.on('error', (err) => {
    console.log('bind ' + port + ':', err.code);
    console.log('a living owner beats socket flags every time');
  });
  s.on('listening', () => {
    console.log('bound ' + port + ' cleanly');
    s.close();
  });
  s.listen(port);
}

tryBind(3999);
Try it live
📊 Production Insight
Teams that cargo-cult socket flags into production configs create restart races that only appear under deploy pressure. Keep default socket behavior for app servers and spend the effort on graceful shutdown, which fixes restarts honestly instead of papering over them.
🎯 Key Takeaway
SO_REUSEADDR only smooths rapid rebinds after closes and Node sets it already. Persistent collisions mean a living process, so find and stop the owner instead of tuning flags.

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.

port-env.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
const http = require('http');
const PORT = Number(process.env.PORT) || 3000;
const HOST = process.env.HOST || '127.0.0.1';

const server = http.createServer((req, res) => {
  res.end('hello from ' + PORT);
});

server.listen(PORT, HOST, () => {
  console.log('listening on http://' + HOST + ':' + PORT);
  console.log('override with: PORT=3001 node port-env.js');
});
Try it live
📊 Production Insight
Shared hosts running staging and production side by side collide precisely because both default to the same port. Distinct PORT values per environment in the process manager config, asserted by a deploy preflight, make the collision structurally impossible instead of merely unlikely.
🎯 Key Takeaway
Read the port from process.env.PORT with a fallback, log the bound port on boot, and document the default. Never leave a literal port inside 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.

💡Screenshot the lsof Output
Before killing anything on a shared host, save the listing output. If the PID belonged to a teammate's debug session, you will want to tell them exactly what you stopped instead of guessing from memory.
📊 Production Insight
The costliest EADDRINUSE incidents are never the error itself but the blind kills around it, including murdered databases and teammates' load tests. A thirty-second listing read is the cheapest insurance in operations.
🎯 Key Takeaway
Read the port, list the owner, stop it through the right channel, confirm freedom, then start. After recovery, add the env-var port and error handler so it stays fixed.
● Production incidentPOST-MORTEMseverity: high

A Stale pm2 Process Blocked Every Deploy for 41 Minutes

Symptom
At 2:08 PM, a routine deploy to one staging server began failing: the new process exited with EADDRINUSE on port 4000 within 3 seconds of starting, three releases in a row. Health checks marked the deploy broken, so the team rolled back twice and re-examined the release diff, which contained only a copy change. The API served stale responses throughout because an orphaned process from a killed pm2 daemon still owned the port and answered traffic.
Assumption
The team assumed pm2 restart fully replaced the process tree, since pm2 list showed the app as online and logs streamed normally. The deploy script also assumed a free port because it checked with curl after starting the new process, which answered fine, served by the orphan. Nobody suspected two processes because the server had only 40 percent memory use and no alert fired.
Root cause
A week earlier, an engineer had run pm2 kill during an incident and restarted the app manually with node server.js & to restore service fast. That manual process owned port 4000 with PID 18412. Every later pm2-managed deploy started a new process that crashed on bind in under 3 seconds, while the forgotten manual process kept serving week-old code. Two overlapping supervisors, pm2 and a human with nohup, owned one port's lifecycle.
Fix
The engineer ran lsof -i :4000, found PID 18412 with no pm2 parent, killed it, and the next deploy bound cleanly in 1 second. The team then banned manual starts on servers, added a deploy preflight that fails when lsof -i :$PORT returns a PID outside the supervisor, and moved the port into a PORT env var so staging and production no longer share 4000 on shared hosts.
Key lesson
  • 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.
Production debug guideFive steps that name the process on your port and free it without collateral damage.5 entries
Symptom · 01
Node exits with listen EADDRINUSE: address already in use :::3000
→
Fix
Name the owner before touching anything: lsof -i :3000 -sTCP:LISTEN on macOS and Linux, or netstat -ano | findstr :3000 on Windows. Note the PID and process name. If it is your own stale server, stop that terminal or window instead of killing, so shutdown hooks run.
Symptom · 02
The PID belongs to a process you recognize
→
Fix
Stop it gracefully first: kill <pid> (SIGTERM) and wait 5 seconds, then lsof -i :3000 again to confirm the port freed. Use kill -9 <pid> only when SIGTERM fails, since SIGKILL skips cleanup and can leave sockets in TIME_WAIT for 60 seconds. On Windows use taskkill /PID <pid> /F as the last resort.
Symptom · 03
The port refills instantly after every kill
→
Fix
A supervisor is respawning it. Check pm2 list, systemctl status <service>, and Docker with docker ps --filter publish=3000. Stop the extra supervisor instead of the process: pm2 stop <app>, systemctl stop <service>, or docker stop <container>. Two supervisors for one port guarantees a repeat.
Symptom · 04
nodemon crashes with EADDRINUSE on every save
→
Fix
Your code listens twice per boot or the old process outlives the restart. Ensure server.listen runs once at the module top level, not inside a callback that reruns. Add --delay 500 to nodemon for slow-shutdown apps, and confirm no second terminal runs the same app.
Symptom · 05
You need the app up right now and the owner is unclear
→
Fix
Start on a free port explicitly: PORT=3001 npm start, then confirm with lsof -i :3001. Treat this as triage, not a fix. Circle back the same day to identify the squatter, or stale processes accumulate until the machine runs out of memory.
EADDRINUSE Causes Compared
Root CauseHow to ConfirmFixPrevention
Stale instance of your own applsof shows node started from your project directoryStop the old terminal or kill the PIDOne terminal per app plus shutdown hooks
Second project on the same portlsof shows a different project path or commandStart yours with PORT=3001PORT env var convention in every project
nodemon old process outlives restartCollision appears on every file saveListen once, honor SIGTERM, add restart delayClose servers in teardown and set timeouts
Supervisor respawns the squatterPort refills seconds after every killStop it via pm2, systemd, or docker stopOne supervisor per port, asserted by deploy preflight
Docker host-port mapping clashlsof shows docker-proxy on the portRemap with -p 3001:3000 or stop the containerDistinct published ports per environment
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
port-server.jsconst http = require('http');What EADDRINUSE Means
find-port-owner.jsconst PORT = Number(process.argv[2]) || 3000;Finding the Squatter on macOS, Linux, and Windows
listen-once.jsconst http = require('http');nodemon Double-Listens and Other Self-Inflicted Collisions
reuse-check.jsconst net = require('net');SO_REUSEADDR
port-env.jsconst http = require('http');The PORT Env Convention

Key takeaways

1
EADDRINUSE means the kernel rejected a second listener on one IP and port.
2
List the owner with lsof or netstat before signaling anything.
3
Stop processes through their terminal or supervisor, SIGTERM before SIGKILL.
4
Listen once per process and close servers in test teardown.
5
SO_REUSEADDR never shares a live port between two servers.
6
Read PORT from the environment with a fallback in every project.

Common mistakes to avoid

5 patterns
×

Killing the PID without listing the process first

Symptom
Teammates lose debug sessions or databases die, and nobody knows what was stopped.
Fix
Run lsof or tasklist first, confirm the process is safe, and signal politely before forcing.
×

Switching ports without finding the squatter

Symptom
Stale processes pile up, leak memory, and the collision returns on the next port too.
Fix
Always identify the owner with lsof. Treat a port change as triage and circle back the same day.
×

Calling listen inside handlers, retries, or test hooks

Symptom
The first boot works and every later call throws EADDRINUSE from the same process.
Fix
Listen once per process lifetime. Reuse the server instance and close it in teardown.
×

Assuming SO_REUSEADDR lets two servers share a port

Symptom
Socket flags get tuned while the living squatter keeps the port and nothing changes.
Fix
Leave default socket behavior alone. Find the owner process and stop it.
×

Hardcoding port 3000 in every project

Symptom
Every new checkout collides on a busy dev machine and developers memorize random ports.
Fix
Use process.env.PORT || 3000 everywhere and document each project's default port.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does EADDRINUSE mean and which layer raises it?
Q02JUNIOR
How do you find which process owns port 3000?
Q03SENIOR
Why does nodemon hit EADDRINUSE on every save?
Q04SENIOR
Does SO_REUSEADDR let two servers share a port?
Q05SENIOR
How should tests avoid port collisions when run in parallel?
Q01 of 05JUNIOR

What does EADDRINUSE mean and which layer raises it?

ANSWER
The kernel refuses a second TCP listener on one IP and port pair, and Node surfaces the errno as a server error event. It means another socket owns the address, so the fix is finding that owner, not changing application logic.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Should I just reboot when EADDRINUSE appears?
02
Why does the collision happen only sometimes?
03
Can two Node apps share one port with a cluster?
04
What is TIME_WAIT and does it cause this?
05
How do Docker port collisions differ?
06
Is kill -9 safe for a stuck server?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.

Follow
✓ Verified
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
🔥

That's Node.js. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
CORS Preflight Response Fix
22 / 30 · Node.js
Next
Unexpected Token JSON Parse Fix
→