The Best Way to Manage Ports in 2025

A deep dive into why port conflicts happen, what tools exist, and how to prevent them.

10 min read · Updated January 2025

The Port Conflict Problem

Modern development stacks are complex. A typical project might run:

That is five ports for one project. If you work on multiple projects, or if any of these processes crash without releasing their port, you hit conflicts. You have all seen Error: listen EADDRINUSE.

Why Port Conflicts Keep Happening

Zombie processes

The most common cause. You Ctrl+C your dev server, but a child process (Webpack, esbuild, a database connection pool) keeps running in the background. The parent exited but the port is still held.

Crashed servers

Your server crashed during a hot reload. Node.js exits but the port stays in TIME_WAIT for 30-60 seconds. Or worse, a subprocess survived the crash and is still bound to the port.

Docker leftover containers

You ran docker-compose up yesterday. Today, those containers are still running and holding ports 5432, 6379, and 3000.

Multiple projects, same defaults

Every React app defaults to port 3000. Every Express app defaults to port 3000. You open two projects and immediately conflict.

Port Management Tools Compared

ToolKill PortScanSafetyRestartConfigCross-platform
lsof + killManualNoNoNoNomacOS/Linux
kill-port (npm)YesNoNoNoNoYes
fkill-cliYesNoNoNoNoYes
portrmYesYes3-tierYes.ptrm.tomlYes

The Ideal Developer Workflow

Here is a port management setup that eliminates conflicts permanently:

Step 1: Install portrm globally

$ npm i -g portrm
# or: brew install abhishekayu/tap/portrm

Step 2: Add a .ptrm.toml to each project

# .ptrm.toml
[project]
name = "my-app"

[services.frontend]
port = 3000
run = "npm run dev"
cwd = "./frontend"
preflight = true

[services.api]
port = 8080
run = "cargo run"
cwd = "./backend"
preflight = true

[profiles.staging]
frontend = { port = 3100 }
api = { port = 8180, env = { RUST_LOG = "info" } }

Step 3: Add pre-scripts to package.json

{
  "scripts": {
    "predev": "ptrm fix 3000 3001 -y",
    "dev": "concurrently \"next dev\" \"node api/server.js\"",
    "ports": "ptrm scan",
    "ports:clean": "ptrm fix 3000 3001 5432 6379 -y"
  }
}

Step 4: Use ptrm scan as a diagnostic

$ ptrm scan
# Shows all occupied ports with service names, PIDs, memory, safety levels

When something is not working, ptrm scan gives you immediate visibility into what is running.

Port Management in CI/CD

Port conflicts in CI are particularly painful because you cannot easily debug them. Common scenarios:

Parallel test runners

Jest, Vitest, and Playwright can run test suites in parallel. Each suite might spin up a test server. If they all try port 3000, tests fail randomly.

# In your CI script, clear ports before tests:
$ npx portrm fix 3000 4000 5000 -y
$ npm test

Leftover processes from previous builds

Self-hosted CI runners can have leftover processes from previous builds:

# CI cleanup step:
$ npx portrm fix 3000 8080 5432 -y || true

Team-Level Port Management

For teams working on microservices or monorepos with multiple services:

Standardize port assignments

Create a port registry in your project documentation:

ServiceDev PortTest Port
Frontend30004000
API80809080
Auth service80819081
PostgreSQL54325433
Redis63796380

Commit .ptrm.toml to your repo

This ensures every developer on the team has the same port configuration and safety rules. New team members can run ptrm up and get the full stack running without conflicts.

Use portrm in onboarding docs

# New developer setup:
$ git clone repo && cd repo
$ npm install
$ ptrm scan          # Check for conflicts
$ ptrm fix 3000 -y   # Clear if needed
$ npm run dev         # Start development

Take control of your ports

One tool for scanning, fixing, and preventing port conflicts across your entire workflow.