Fix EADDRINUSE: Address Already in Use in Node.js
Quick answer
Node.js throws EADDRINUSE when another listener already owns the requested address and port, such as :::3000. Identify the owner before stopping it, then either shut down the stale process, fix a duplicate server.listen call, or choose a deliberate host port. Treat Docker, WSL, tests, CI, and production as separate binding layers.
You start a Node.js server and receive:
Error: listen EADDRINUSE: address already in use :::3000This is an operating-system bind failure, not an HTTP, npm, Express, or Next.js failure. Node asked the kernel to reserve an address and port; the kernel found an existing owner and refused. The fast fix may be to stop a stale development server. The durable fix is to prove who owns the socket and address the layer that created the collision.
That distinction matters. Killing every Node process can interrupt a database
proxy, another checkout, or a colleague's shared service. Switching every app
to a new number only hides a duplicate listen() call. The sections below walk
through safe diagnosis first, then framework, Docker, WSL, CI, and production
fixes.
Quick Answer
Node.js throws EADDRINUSE when another listener already owns the requested
address and port, such as :::3000. Identify the owner before stopping it, then
either shut down the stale process, fix a duplicate server.listen call, or
choose a deliberate host port. Treat Docker, WSL, tests, CI, and production as
separate binding layers.
TL;DR
EADDRINUSEmeans an address-and-port bind was rejected because it is already in use.:::3000is an IPv6 all-interfaces bind. It may also occupy IPv4 port 3000 through dual-stack behavior.- Inspect the listener and command line before you stop anything.
- A second
app.listen()orserver.listen()is a code bug, not a port-choice problem. - In Docker,
3000:3000claims a host port; the container's internal port is not the conflicting resource. - Use
port: 0in parallel tests. Let the OS choose a free port and read it fromserver.address(). - In production, fail clearly or use platform-assigned
PORT. Do not loop forever retrying a port that belongs to another deployed service.
Symptoms
The exact address changes with the host and runtime, but the failure has the same meaning:
| Symptom | What Node tried to bind | Likely layer |
|---|---|---|
address already in use :::3000 | IPv6 unspecified address, TCP 3000 | Local dev server, dual-stack collision |
address already in use 0.0.0.0:3000 | All IPv4 interfaces, TCP 3000 | Another host-level listener |
address already in use 127.0.0.1:3000 | Local loopback only, TCP 3000 | Local tool or test server |
Docker Bind for 0.0.0.0:3000 failed | Docker host-port publication | Container or host service |
| Tests fail only in CI or parallel mode | A shared hard-coded test port | Test worker/process leak |
| Server starts once, then crashes during reload | A second bootstrap path calls listen | App lifecycle or watcher bug |
This error happens before a client can connect. A browser showing ECONNREFUSED
means no process accepted the connection; EADDRINUSE means your new process
could not start accepting connections at all.
Common Causes
| Cause | Why it creates a collision | Best fix |
|---|---|---|
A stale npm run dev process | The old process is still listening after a terminal or editor change | Identify it, stop it gracefully, verify the port is free |
| Two terminals start the same app | Both request the same default port | Keep one instance or give each checkout a defined port |
Duplicate listen() calls | Two server objects in one app claim the same port | Keep one process-level listener |
Docker ports: ["3000:3000"] | Docker claims host port 3000 | Stop the owning container or map a different host port |
| WSL or desktop port forwarding | Windows and the Linux VM expose related localhost ports | Inspect both Windows and WSL listeners |
| Parallel tests | Each worker binds a fixed port | Use port 0 and close every server |
| CI jobs sharing a host | Concurrent jobs use a static port | Isolate jobs or allocate a dynamic port |
Cloud runtime PORT is ignored | The platform expects one assigned listener | Bind process.env.PORT and the required host |
| A process manager restarts an old release | Two process groups overlap during deploy | Use the manager's handoff or rolling-deploy behavior |
Root Cause: A Bind Is Exclusive
A TCP listener is identified by more than a number. The kernel considers the protocol, local address, port, and platform-specific socket options. In the ordinary Node.js server case, only one process can listen on the same effective address and TCP port.
Node's server.listen() is asynchronous. A successful bind produces the
listening event. When another server already owns the requested
port/address/handle, Node emits EADDRINUSE instead. The Node documentation
also notes that calling listen() again on the same server before close()
raises ERR_SERVER_ALREADY_LISTEN—a useful clue when the duplication occurs
inside one process.
What :::3000 actually means
The three colons are not a typo. :: is the IPv6 unspecified address, analogous
to IPv4 0.0.0.0. :::3000 means “IPv6 all interfaces, TCP port 3000.”
With Node's default ipv6Only: false, binding :: can provide dual-stack
behavior: the same listener may accept IPv4 and IPv6 connections. The exact
interaction depends on the operating system. That is why a process displayed as
*:3000, 0.0.0.0:3000, or :::3000 can block what looks like a different
address family.
Do not “fix” this by randomly forcing IPv4 or IPv6. Bind 127.0.0.1 for a
local-only development service, 0.0.0.0 when a container or LAN-reachable
development service needs IPv4, or an explicit ::/ipv6Only policy where IPv6
is intentional. The host address is part of the deployment contract.
Node version details and the TIME_WAIT myth
The ipv6Only listen option was added in Node.js 11.4.0. Current Node
documentation lists reusePort support from Node 22.12.0 and 23.1.0, and only
on operating systems that implement it. Check node --version before copying
a deployment setting from a newer runtime into an older service.
Do not treat TIME_WAIT, SO_REUSEADDR, or reusePort as a universal fix
for this error. A TIME_WAIT connection is not a LISTEN socket that can
serve requests. Node already sets SO_REUSEADDR on its sockets, and
reusePort deliberately distributes traffic only where the OS supports it.
For EADDRINUSE on TCP 3000, inspect the active listener and the process
lifecycle first.
Step-by-Step Solution
Step 1: Confirm that the failure is a listener collision
Capture the full error and the exact address. Do not assume that every
EADDRINUSE involving 3000 belongs to the application you just ran.
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (node:net:...)Then inspect listening TCP sockets. Filtering to listeners avoids mistaking an outgoing connection for a server that owns the port.
Step 2: Identify and verify the owning process
macOS and Linux
Run this in the host environment where the failing Node command runs:
lsof -nP -iTCP:3000 -sTCP:LISTEN
# Example output:
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# node 48291 alex 21u IPv6 0x1234567890 0t0 TCP *:3000 (LISTEN)
ps -p 48291 -o pid=,ppid=,user=,command=lsof shows the owner; ps gives you a second check before ending it. On
Linux, this alternative can expose the program and PID when permissions allow:
ss -ltnp 'sport = :3000'If the command line identifies a stale local development server, request normal termination first:
kill -TERM 48291
lsof -nP -iTCP:3000 -sTCP:LISTENThe second command should return no listener. Use kill -KILL 48291 only after
you have confirmed the PID and the process fails to exit. A forceful signal
skips cleanup code, which can lose logs, leave files half-written, and hide a
shutdown bug you will meet again in production.
Windows PowerShell
PowerShell exposes the owning PID directly:
Get-NetTCPConnection -LocalPort 3000 -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess
Get-Process -Id 48291
Stop-Process -Id 48291
Get-NetTCPConnection -LocalPort 3000 -State ListenReplace 48291 with the OwningProcess value you inspected. The final command
should produce no output. Stop-Process requests termination; reserve
Stop-Process -Force for a verified process that refuses to stop.
On older Windows environments, Command Prompt provides the equivalent:
netstat -ano | findstr :3000
tasklist /FI "PID eq 48291"
taskkill /PID 48291netstat may display IPv4 and IPv6 rows. Use the LISTENING entry and check
the PID with tasklist before taskkill.
Step 3: Choose the correct fix, not the quickest-looking command
Use the evidence from the previous step:
- A stale local process owns the port. Stop that verified process, then start the intended app once.
- A legitimate different service owns it. Assign your app a documented alternate port, such as 3001, rather than disrupting that service.
- Your app owns it twice. Fix the bootstrap code. Port changes only defer the failure to the next port.
- Docker, WSL, CI, or a platform owns it. Fix the host mapping or allocation at that layer. Killing a process inside a container cannot free a host port held by a different container.
Step 4: Change the port deliberately when two services should coexist
For a simple Node HTTP service, validate an environment-provided port and keep the default local:
const http = require("node:http")
function getPort(value) {
const port = Number.parseInt(value ?? "3000", 10)
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error("PORT must be an integer between 1 and 65535")
}
return port
}
const port = getPort(process.env.PORT)
const host = process.env.HOST ?? "127.0.0.1"
const server = http.createServer((request, response) => {
response.writeHead(200, { "content-type": "text/plain; charset=utf-8" })
response.end("healthy\n")
})
server.listen({ host, port }, () => {
console.log("Listening on http://" + host + ":" + port)
})Run a second local copy on a different port:
PORT=3001 node server.js
# Expected output:
# Listening on http://127.0.0.1:3001In PowerShell, set the environment variable for the current session:
$env:PORT = "3001"
node server.jsThe explicit 127.0.0.1 binding keeps this sample local. A container usually
needs HOST=0.0.0.0 so the Docker network can reach Node inside the container.
Do not bind broadly just because it is a familiar incantation; it changes who
can reach the service.
Fix a Duplicate listen() Call in Your Application
The following pattern creates two HTTP servers that both request port 3000:
const express = require("express")
const http = require("node:http")
const app = express()
const port = 3000
app.get("/", (request, response) => {
response.send("hello")
})
app.listen(port)
http.createServer(app).listen(port)app.listen() already creates and starts an HTTP server. The second call
therefore fails. Keep one listener. If you need the Server object for
WebSockets or graceful shutdown, create it yourself and listen once:
const express = require("express")
const http = require("node:http")
const app = express()
const port = 3000
app.get("/", (request, response) => {
response.send("hello")
})
const server = http.createServer(app)
server.listen(port, "127.0.0.1", () => {
console.log("Listening on http://127.0.0.1:3000")
})Search every startup path, not just the main file:
rg -n "(\.listen\(|createServer\()" src app server testCommon real-world sources are an imported module that starts itself as a side
effect, a test helper also imported by production code, an if (require.main === module) guard missing from a CLI entry point, and a reload
hook that calls bootstrap twice. A module that exports createApp() or
createServer() is easier to test than one that binds during import.
For related configuration failures, see the guide to fixing a missing dotenv module and the React 19 upgrade notes, which are useful when a frontend and backend share a local environment.
Minimal Reproduction and a Test-Safe Fix
This complete script proves the kernel behavior without relying on Express, Next.js, or a shell process. The first server asks the OS for an available ephemeral port, then the second server tries to claim the same address and port.
const http = require("node:http")
function listen(server, options) {
return new Promise((resolve, reject) => {
const onError = (error) => reject(error)
server.once("error", onError)
server.listen(options, () => {
server.off("error", onError)
resolve()
})
})
}
function close(server) {
return new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()))
})
}
async function main() {
const first = http.createServer()
await listen(first, { host: "127.0.0.1", port: 0 })
const address = first.address()
const port = address.port
const second = http.createServer()
try {
await listen(second, { host: "127.0.0.1", port })
} catch (error) {
console.log(error.code)
console.log(error.address + ":" + error.port)
} finally {
await close(first)
}
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
// Expected output:
// EADDRINUSE
// 127.0.0.1:<an OS-assigned port>port: 0 does not mean “listen on port zero.” It asks the OS to assign an
available ephemeral port. Read the real port only after the listening callback
or listening event. This is the right default for unit and integration tests
that run in parallel.
Bad test pattern
const server = app.listen(3000)Correct test pattern
const server = app.listen(0, "127.0.0.1")
server.on("listening", () => {
const port = server.address().port
console.log("Test server port:", port)
})Close the returned server in your test framework's afterEach, afterAll, or
finally block. A dynamically assigned port avoids collisions, while cleanup
prevents hanging test runners and leaked handles.
Next.js, npm Scripts, and Development Servers
Next.js development defaults to port 3000. When another application should stay on 3000, pass a deliberate alternative:
npm run dev -- --port 3001
# Expected output includes a Local URL with port 3001.For a standalone Next.js command, the equivalent is next dev -p 3001. Keep
this choice in a team script or documented local environment rather than asking
each developer to remember an untracked command.
Do not run next dev and a custom Node server that both listen on 3000 unless
they are meant to be separate services with different ports. A Next.js custom
server also needs one owner of startup. The same rule applies to Vite, CRA,
nodemon, tsx watch mode, and IDE launch configurations: a watcher should
restart one child process, not accumulate children.
If a port conflict appears only after a hot reload, log the PID at startup:
console.log("Starting server process", process.pid)Two different PIDs suggest two processes or a watcher issue. The same PID with multiple bootstrap logs points to duplicate startup within one process. This small distinction saves a surprising amount of time.
For frontend request failures after the server does start, use this guide to debouncing React requests and the HTTP 429 troubleshooting guide. They address client traffic behavior; neither is a cure for a port bind failure.
Docker, Compose, and WSL
Docker: distinguish the host port from the container port
This Compose configuration runs Node on port 3000 inside the container and publishes it as port 3000 on the host:
services:
web:
build: .
environment:
HOST: 0.0.0.0
PORT: "3000"
ports:
- "3000:3000"The left side is the host port; the right side is the container port. If another container or local process owns host 3000, change only the host mapping:
services:
web:
build: .
environment:
HOST: 0.0.0.0
PORT: "3000"
ports:
- "3001:3000"Then visit http://localhost:3001. Inspect existing mappings before stopping a
container:
docker ps --format "table {{.Names}}\t{{.Ports}}"
docker compose ps
docker compose port web 3000The Docker CLI reports the host-to-container association. In the Compose sample,
web is the service name; docker compose port resolves its generated
container name for you. Stop only the container you have verified is obsolete.
EXPOSE 3000 in a Dockerfile documents
an intended container port; by itself it does not reserve or publish host port 3000. Docker's port-publishing documentation also warns that publishing without
a loopback address can expose the port beyond the host. For a local-only service,
prefer 127.0.0.1:3001:3000 where your Docker setup supports it.
WSL: inspect both sides of the boundary
WSL 2 runs Linux in a managed VM and integrates localhost with Windows. A port
may be held by a Linux process, a Windows process, or a forwarded/proxied
listener. Run the Linux lsof command inside WSL, then run the PowerShell
Get-NetTCPConnection command on Windows. Do not assume the result from one
environment describes the other.
If your app is intended to be reached from Windows, use an explicit host policy
and test from the actual client. Avoid a blind 0.0.0.0 change: it may make the
service reachable from network interfaces you did not mean to expose.
Production, Cloud, CI/CD, CPU, and GPU Notes
Production is not the place to kill whichever PID happens to own a port. A
reverse proxy, load balancer, container orchestrator, or process manager may
intentionally own the public socket. Your application normally binds a
platform-provided PORT, while the platform handles routing and rollout.
Use one server object and close it on termination signals:
const http = require("node:http")
function getPort(value) {
const port = Number.parseInt(value ?? "3000", 10)
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error("PORT must be an integer between 1 and 65535")
}
return port
}
const port = getPort(process.env.PORT)
const host = process.env.HOST ?? "0.0.0.0"
const server = http.createServer((request, response) => {
response.writeHead(200, { "content-type": "text/plain; charset=utf-8" })
response.end("ok\n")
})
server.once("error", (error) => {
console.error("Server failed to listen:", error.code, error.message)
process.exitCode = 1
})
server.listen({ host, port }, () => {
console.log("Server listening on " + host + ":" + port)
})
let shuttingDown = false
function shutdown(signal) {
if (shuttingDown) return
shuttingDown = true
console.log("Received " + signal + "; stopping new connections")
server.close((error) => {
if (error && error.code !== "ERR_SERVER_NOT_RUNNING") {
console.error("Shutdown failed:", error)
process.exitCode = 1
}
})
}
process.once("SIGTERM", () => shutdown("SIGTERM"))
process.once("SIGINT", () => shutdown("SIGINT"))server.close() stops accepting new connections and gives existing work a chance
to complete. Real services should also set an operational shutdown deadline and
close database, queue, and WebSocket clients in the right order. Do not add
unbounded automatic retries for EADDRINUSE in production: another release
might own the port permanently, and a retry loop conceals a bad deployment.
For Node cluster workers, use Node's supported shared-server behavior or a
platform load balancer. Do not independently launch unrelated workers and hope
they can all bind the same port. reusePort exists only on supported platforms
and is a deliberate traffic-distribution feature, not a generic workaround.
In CI, a static port works only when every job has network isolation. Parallel
workers on one runner should use port 0, a per-job allocated port, or container
network isolation. In cloud deployments, bind the assigned PORT and verify the
health-check path. CPU architecture, GPU availability, and CUDA drivers do not
change EADDRINUSE; it is a host networking ownership problem. See
why PyTorch CUDA may be unavailable
only when a separate GPU startup issue prevents your app from reaching the bind
step.
Verification Steps
After applying a fix, verify all of the following:
-
The port has one expected listener.
lsof -nP -iTCP:3000 -sTCP:LISTENThe command should show the intended process, or no process before startup.
-
The startup log reports the expected host and port. Do not infer success from the absence of a stack trace; wait for the
listeningcallback. -
A local health request reaches the intended server.
curl --fail --silent --show-error http://127.0.0.1:3000/Expected output for the example server:
healthy -
A second startup behaves predictably. If it should coexist, it uses its configured alternate port. If it should replace the first instance, the process manager performs a graceful handoff rather than a collision.
-
Tests and containers clean up. Run the test suite twice and bring the Compose stack up/down twice. A failure only on the second run usually signals leaked server handles or a lingering container.
Troubleshooting Matrix
| Observation | Most likely cause | Check | Correct action |
|---|---|---|---|
lsof shows your old node PID | Stale dev server | Command line and parent PID | Terminate it gracefully |
lsof shows an IDE, proxy, or another app | Legitimate port owner | Product/process name | Preserve it; choose a new app port |
| No host PID, but Docker fails | Docker mapping conflict | docker ps and docker port | Stop verified container or remap host port |
| Same Node PID logs startup twice | Duplicate bootstrap | Search .listen( | Refactor to one listener |
| CI fails only with parallel workers | Static test port | Worker count and open handles | Use port 0 and cleanup |
| WSL works in Linux but fails from Windows | Forwarding/bind policy | Inspect both OSes | Adjust host and verify real client path |
| Cloud health check fails though local works | Ignored assigned port or host | Runtime PORT and docs | Bind the platform port and required interface |
| Changing ports never helps | Multiple startup paths or security tool | PIDs/command lines each attempt | Fix ownership and process lifecycle |
Prevention
Use this checklist in projects that run a Node server:
- Centralize process startup in one module. Export app construction separately from server binding.
- Read
PORTandHOSTfrom validated configuration. Document local defaults. - Print PID, host, port, and environment once at startup without logging secrets.
- Bind
127.0.0.1for local-only tools. Review any0.0.0.0or public Docker publication as a security decision. - Give each local checkout an intentional port, or use a script that assigns it.
- Use port 0 for test servers and close every handle in test cleanup.
- Stop dev processes with
Ctrl+Cso they receiveSIGINTand can close cleanly. - In Docker Compose, name host and container ports in documentation and avoid publishing services that only need internal container networking.
- In CI/CD, use isolated networks, per-job ports, or deployment-native handoff instead of shell commands that kill broad groups of processes.
For a broader debugging toolkit, these related guides are useful:
- common JavaScript coding patterns
- handling React input and request pressure
- fixing missing dotenv configuration
- debugging JSON serialization at an API boundary
- resolving a Hugging Face authentication failure
- fixing an unavailable PyTorch CUDA runtime
Official References
- Node.js
net.Server.listen()documentation - Node.js error code documentation
- Next.js CLI documentation
- Docker port publishing and mapping
- Microsoft
Get-NetTCPConnectiondocumentation
FAQs
How do I fix Error: listen EADDRINUSE: address already in use :::3000?
Find the process listening on TCP 3000, verify its command line, then stop it
gracefully if it is stale. On macOS/Linux, use lsof -nP -iTCP:3000 -sTCP:LISTEN. On Windows PowerShell, use Get-NetTCPConnection -LocalPort 3000 -State Listen. If the port is free after inspection, search for duplicate
app.listen or server.listen calls.
What does :::3000 mean in Node.js?
:::3000 means Node tried to bind port 3000 on IPv6's unspecified address
::, representing all IPv6 interfaces. Node's normal dual-stack behavior can
also make this listener cover IPv4. It can therefore conflict with an IPv4
listener on the same host port.
Why does EADDRINUSE return after I kill a process?
The process may restart under nodemon, an IDE, Docker Compose, PM2, a service manager, or a CI worker. Alternatively, the owner may not be Node at all. Inspect the new PID and command line each time; persistent recurrence is a process-lifecycle or host-port mapping problem, not evidence that you need a more forceful kill command.
Is changing from port 3000 to 3001 always safe?
No. It is safe when two services are intentionally meant to coexist and their
clients, proxies, Docker mappings, and environment configuration all use the
new port. It is the wrong fix for one application that calls listen twice or a
cloud service that must bind the platform-provided PORT.
Can Node.js listen on the same port in multiple workers?
Sometimes, but only through an intentional shared-server strategy. Node cluster
can share a server handle, and modern Node supports reusePort on some
platforms. Neither feature makes independently started services safe to place
on one port. Use a reverse proxy, orchestrator, cluster setup, or distinct
ports according to your architecture.
Why does my Docker app fail when Node runs on port 3000 inside the container?
The conflict is usually the host side of 3000:3000. Docker must reserve host
port 3000 before the container starts. Another host process or container can
already own it. Keep the application on container port 3000 and map a free host
port, such as 3001:3000, or remove the public mapping if only other containers
need access.
Key takeaways
- •EADDRINUSE means the OS rejected a bind because an address and port are already owned by a listener.
- •:::3000 is an IPv6 unspecified-address binding; it is often dual-stack and can conflict with IPv4 port 3000.
- •Inspect the PID and command first. Stop a stale process gracefully before using a forceful kill.
- •Fix duplicate app.listen or server.listen calls in code; changing the port only hides that bug.
- •Docker host ports, WSL forwarding, test workers, and CI jobs can collide even when each app appears isolated.
- •Use port 0 for parallel test servers and read back the assigned port after the listening event.
Frequently asked questions
How do I fix Error: listen EADDRINUSE: address already in use :::3000?
Find the process listening on TCP port 3000, confirm it is safe to stop, then terminate it gracefully or use a different intentional port. On macOS and Linux, run lsof -nP -iTCP:3000 -sTCP:LISTEN. On Windows PowerShell, run Get-NetTCPConnection -LocalPort 3000 -State Listen. If no other process owns it, search your code for a duplicate app.listen or server.listen call.
What does :::3000 mean in a Node.js EADDRINUSE error?
:::3000 means Node tried to bind TCP port 3000 on the IPv6 unspecified address ::, which represents all IPv6 interfaces. With Node's default ipv6Only setting of false, that binding may also accept IPv4 traffic through dual-stack behavior. It can therefore conflict with a process that appears to be using IPv4 port 3000.
Why does EADDRINUSE keep happening after I kill Node?
The listener may be a Docker port mapping, a second terminal, an IDE task, a process manager, WSL port forwarding, or your own application calling listen twice. Inspect the listener again after stopping it and examine its command line. Do not assume every process on port 3000 is Node or that force-killing one PID fixes the source of the repeat.
Should I use kill -9 or taskkill /F for EADDRINUSE?
No, not first. Confirm the owning PID and send a normal termination signal so the process can close connections and remove temporary state. Use a forceful kill only when the verified process ignores graceful termination. Never use broad commands that kill every Node process on a shared workstation or production host.
How do I avoid EADDRINUSE in Node.js tests?
Do not hard-code port 3000 in tests that can run in parallel. Listen on port 0 so the operating system assigns a free ephemeral port, wait for the listening event, read server.address().port, and close the server in an afterEach or finally block. This prevents worker and CI collisions.
Why does Docker say port 3000 is already allocated when Node is not running?
Docker publishes a container port onto a host port. Another container, local service, or Docker-proxy may already own host port 3000 even though no Node process runs inside the new container. Inspect docker ps, then stop the owning container or map a different host port, such as 3001:3000. EXPOSE alone does not publish a host port.