How to Change the Port in Next.js (and Fix EADDRINUSE)
Change the default Next.js port when it collides with another process. Step‑by‑step fix with code, verification, and prevention tips.
Photo by Florian Olivo on Unsplash
Next.js binds its dev server to TCP port 3000 unless you tell it otherwise — and on a machine that also runs a local API, a Docker container, or a half-dead previous next dev, that default is exactly where collisions happen. There are three idiomatic ways to move it: an environment variable, a CLI flag, or a custom server wrapper. This post covers all three, plus how to diagnose the crash that sends most people looking.
listen EADDRINUSE: address already in use :::3000#
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.
EADDRINUSE 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.
How Next.js resolves the port#
Next.js resolves the port from the -p/--port flag first, then the PORT environment variable, then falls back to a hard-coded 3000 default. There is no port option in next.config.js, which is why a config or .env file has no effect:
# Override the default 3000 — via the CLI flag…
next dev -p 4000
# …or the PORT environment variable
PORT=4000 next devThat resolution order dictates which of the three methods below wins if you combine them: the -p flag beats the environment variable, which beats the default.
Option 1 — inline PORT environment variable#
Pass PORT directly on the command line — before the next dev command. Next.js's HTTP server starts before .env* files are loaded, so setting PORT inside .env.local has no effect on the listening port.
macOS / Linux (bash/zsh):
PORT=4000 next devWindows PowerShell:
$env:PORT=4000; next devWindows CMD:
set PORT=4000 && next devYou can also export the variable at the OS level (e.g. in ~/.zshrc or via System Properties → Environment Variables on Windows) so every project in your shell inherits it without touching package.json. For most single‑page apps, this inline variable is the least invasive change.
Option 2 — the -p flag in package.json#
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.
Option 3 — a custom server.js wrapper#
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. Pick the one that fits your workflow, add the snippet, and restart the dev server with npm run dev.
Confirming the new port#
Run the development command you just configured:
npm run devYou should see output similar to:
> [email protected] dev /path/to/my-next-app
> next dev -p 4000
▲ Next.js 15.x.x
- Local: http://localhost:4000
- Network: http://192.168.x.x:4000Notice 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 4000If the command returns a line, stop that process (kill -9 <PID>) or choose a different port.
When the replacement port is taken too#
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.
Docker: container port vs host mapping#
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=4000Now the container listens on 4000 internally while the host maps it to 5000.
Keeping ports from colliding across the team#
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#
- AI Integration for Next.js + Supabase Applications
- Deploying Next.js + Supabase to Production
- Next.js 15 Minimum Node.js Version 18.18.0
- How to Structure Your Next.js App Router Project for Scale
- How to set the next/image component to 100% height
- FirebaseError: Expected first argument to collection() — Fix
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 15 requires Node.js 18.18' Build Error
Next.js 15 drops support for older Node versions. Here is how to upgrade to Node.js 18.18.0 or later and fix build errors.
Fix ChunkLoadError: Loading Chunk Failed in Next.js
"ChunkLoadError: Loading chunk 5760 failed" almost always means a user has an old tab open after you shipped a new deploy. The fix is configuration — deploymentId, build IDs, and CDN headers — not a try/catch.
Fix 'Hydration failed' in Next.js: 8 Root Causes (2026)
Hydration mismatch errors breaking your Next.js app? Learn the root causes and 8 proven fixes to eliminate these errors permanently.
Browse by Topic
Find stories that matter to you.
