Fix port setting in Next.js
Change the default Next.js port when it collides with another process. Step‑by‑step fix with code, verification, and prevention tips.
TL;DR#
If you're seeing listen EADDRINUSE: address already in use :::3000, the cause is that another process already occupies the default Next.js port. Fix it by telling Next.js to listen on a free port via an environment variable, a CLI flag, or a small server wrapper.
If that doesn't work, scroll to verify the fix — there are two common variants this guide also covers.
What you'll see#
When you run the default development command, the terminal stops with a stack trace that looks like this:
> next dev
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (node:net:1315:19)
at listenInCluster (node:net:1385:12)
at Server.listen (node:net:1475:7)
at Server.listen (node_modules/next/dist/server/next-server.js:1234:15)
at Object.<anonymous> (node_modules/next/dist/bin/next.js:45:23)
at Module._compile (node:internal/modules/cjs/loader:1126:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1180:10)
It happens right after you execute npm run dev (or yarn dev) on a fresh clone of a Next.js project. The behavior is identical on macOS, Linux, and Windows because the underlying Node.js net module reports the same error code across platforms.
Root cause#
Next.js ships with a built‑in development server that listens on TCP port 3000 unless you tell it otherwise. When the operating system reports EADDRINUSE, it means the bind call failed because something else already owns that socket. Common culprits are:
- A local API server (Express, Fastify, Supabase Edge Functions) running on the same port.
- A Docker container exposing port 3000.
- A previous instance of
next devthat didn’t shut down cleanly.
Because the error bubbles up during the server startup phase, the process exits before any page rendering occurs, leaving you with the stack trace above and no hot‑reload.
The relevant code path lives in next/dist/server/next-server.js, where the listen method forwards the port argument (default 3000) to Node’s net.Server.listen. If you don’t provide a custom port, the default is hard‑coded.
// node_modules/next/dist/server/next-server.js (simplified)
class NextServer {
constructor(opts) {
this.port = opts.port || 3000; // <-- default
// …
}
async start() {
await this.server.listen(this.port, this.hostname);
console.log(`> Ready on http://${this.hostname}:${this.port}`);
}
}
The fix#
There are three idiomatic ways to override the default port. I’ll show each with a concrete snippet, then explain why they work.
1. Environment variable (.env.local)#
Create a file named .env.local at the project root and add:
# .env.local
PORT=4000
Next.js automatically loads variables from .env* files at startup, and process.env.PORT is read by the internal server code before the default is applied.
2. CLI flag#
Modify the dev script in package.json to pass the -p flag:
{
"scripts": {
"dev": "next dev -p 4000",
"build": "next build",
"start": "next start"
}
}
The -p (or --port) flag is parsed by the Next.js binary and overrides any environment variable.
3. Custom server wrapper (useful for complex setups)#
If you need to run additional middleware (e.g., Supabase auth) before handing control to Next.js, create server.js:
// server.js
const { createServer } = require('http');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const PORT = parseInt(process.env.PORT, 10) || 4000;
app.prepare().then(() => {
createServer((req, res) => {
// Example: inject a header for every request
res.setHeader('X-Custom-Header', 'iloveblogs');
handle(req, res);
}).listen(PORT, (err) => {
if (err) throw err;
console.log(`> Custom server listening on http://localhost:${PORT}`);
});
});
Then change the dev script:
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "next start"
}
}
All three approaches tell the Next.js runtime to bind to 4000 (or any port you choose) instead of the colliding 3000.
Step by step#
- Decide which method fits your workflow. For most single‑page apps, the
.env.localapproach is the least invasive. - Add the snippet above to the appropriate file (
.env.local,package.json, orserver.js). - If you edited
package.json, runnpm install(oryarn) to refresh the lockfile—this isn’t strictly required but keeps the workspace tidy. - Restart the dev server with
npm run dev. - Verify the console output shows the new port.
Verify the fix#
Run the development command you just configured:
npm run dev
You should see output similar to:
> my-next-app@0.1.0 dev /path/to/my-next-app
> next dev -p 4000
ready - started server on http://localhost:4000
Notice that the error stack trace is gone and the URL reflects the port you set. Open http://localhost:4000 in a browser; the app should load exactly as before.
If you still encounter EADDRINUSE, double‑check that the new port isn’t also taken. You can scan for listening ports with:
lsof -iTCP -sTCP:LISTEN -P | grep 4000
If the command returns a line, stop that process (kill -9 <PID>) or choose a different port.
Variant A — Port already in use#
Sometimes the port you pick is also occupied (e.g., you chose 4000 but a local Supabase emulator runs there). In that case, repeat the verification step with a different number, such as 5000. The same code changes apply; only the numeric value changes.
Variant B — Running in Docker#
When you containerize the app, the host‑side port mapping (docker run -p 3000:3000) may conflict with the container’s internal port. The fix is to expose a different internal port in Dockerfile or docker-compose.yml and keep the Next.js configuration aligned:
# docker-compose.yml
services:
web:
build: .
ports:
- "5000:4000" # host:container
environment:
- PORT=4000
Now the container listens on 4000 internally while the host maps it to 5000.
Why this happens (and how to avoid it next time)#
The root invariant is that only one process can bind to a given TCP port on a host at a time. Because Next.js defaults to a well‑known development port, the likelihood of a clash grows as you add more local services (Supabase, Redis, mock APIs). To stay ahead:
- Add a
PORTentry to.env.exampleso every teammate knows which variable to set. - Include a pre‑flight script in
package.jsonthat checks port availability:
// scripts/check-port.js
const net = require('net');
const port = parseInt(process.env.PORT, 10) || 3000;
const server = net.createServer();
server.once('error', err => {
if (err.code === 'EADDRINUSE') {
console.error(`Port ${port} is already in use. Choose another port or stop the conflicting process.`);
process.exit(1);
}
});
server.once('listening', () => {
server.close();
});
server.listen(port);
Then run it before next dev:
{
"scripts": {
"predev": "node scripts/check-port.js",
"dev": "npm run predev && next dev"
}
}
- Document the chosen port in your onboarding guide and reference it when you write about deployment. For example, see my article on Deploying Next.js + Supabase to Production where I lock the production port to
8080and keep the dev port configurable.
By making the port explicit and checking it early, you eliminate the surprise EADDRINUSE crash.
Related#
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Fix Next.js 16: Disable Turbopack Production Build (2026)
Next.js 16 makes Turbopack the default builder. If `next start` crashes or pages 500 after a Turbopack production build, here's the exact way to opt out per-build, per-environment, or per-project — and the symptoms that mean you should.
Fix Next.js revalidatePath Not Working in Server Actions
Your Server Action mutates data but the page shows stale values until you hard-refresh. `revalidatePath` is one of those APIs that "succeeds" while doing nothing. Here are the six reasons it no-ops, with the exact fix for each — including the one nobody tells you about: `dynamic = 'force-static'`.
Fix Next.js Module Not Found After Deploy or Production Build
Module not found errors only in production? Learn why Next.js builds fail after deploy and get 6 proven fixes that work on Vercel, AWS, and other platforms.
Browse by Topic
Find stories that matter to you.
