portctl 0.5.3

Fix port conflicts, debug processes, and recover dev environments instantly
portctl-0.5.3 is not a library.
$ portctl fix 3000

  ⚡ Port 3000 in use
  → Next.js (PID 81106) running for 7m 21s
  → 🛡 safe to kill

  • Sending SIGTERM to PID 81106
  • Verified: PID 81106 has exited

  ✔ Port 3000 is now free
  Restart: npm run dev

The Problem Every Developer Hits

You've seen this before:

Error: listen EADDRINUSE: address already in use :::3000

A crashed dev server. A zombie process. Something unknown squatting on your port.

So you do the ritual:

lsof -i :3000           # wall of text
kill -9 <pid>            # hope it's not PostgreSQL
# ...was that important?
# how do I restart this thing?

This is fragile. It's blind. It tells you nothing about what you just killed.

portctl replaces this entire workflow with one command. A single CLI tool for port conflict resolution, process inspection, and dev server recovery.


Instant Port Inspection

$ portctl 3000

  ⚡ Port 3000 in use
  → Next.js (PID 81106)
  → running for 7m 21s
  → memory 42.9 MB
  → detected Next.js (95% confidence)
  → 🛡 safe to kill

  📂 Detected project: Next.js
  → dev command: npm run dev
  → default port: 3000

portctl tells you what is running, whether it's safe to kill, and how to restart it.


Port Debugging: Before vs After

Task Before After
Port 3000 stuck lsof -i :3000 | awk ... | xargs kill -9 portctl fix 3000
"What's on my ports?" lsof -iTCP -sTCP:LISTEN (unreadable) portctl scan
"Is this safe to kill?" Google the process name portctl tells you
"How do I restart?" Dig through package.json portctl shows the command
Zombie processes Hunt them one by one portctl doctor -y
Which port is my frontend? Check config files portctl group
Start entire dev stack Open 5 terminals, run commands manually portctl up
Stop everything Find and kill each process portctl down
"Is my port free?" lsof -i :3000 and parse output portctl preflight 3000
Monitor a flaky server Watch logs + manual restart portctl watch 3000
Duplicate port assignments Manually diff config files portctl registry check
Run all checks in CI Write custom scripts portctl ci
Switch dev/staging config Edit .env files manually portctl use staging

Install

Native binary. No Node. No runtime dependencies. ~1.2MB, runs instantly.

One-line install (recommended)

curl -fsSL https://raw.githubusercontent.com/abhishekayu/portctl/main/install.sh | sh

Package managers

Platform Command
Homebrew brew install abhishekayu/tap/portctl
Cargo cargo install portctl
npm npm install -g portctl
Scoop (Windows) scoop bucket add portctl https://github.com/abhishekayu/scoop-portctl && scoop install portctl
Debian/Ubuntu Download .deb from releases

Try instantly (no install)

npx portctl scan

Build from source

git clone https://github.com/abhishekayu/portctl.git
cd portctl
cargo install --path .

Supports macOS (Intel + Apple Silicon), Linux (x86_64 + ARM64), and Windows.


Usage Examples

Scan all listening ports

$ portctl scan

  ⚡ 5 active ports

  PORT    PROCESS                PID      SERVICE        MEMORY     UPTIME     USER
  ────────────────────────────────────────────────────────────────────────────────
  3000    node                   81106    Next.js        42.9 MB    7m 17s     abhishek
  3898    Code Helper (Plugin)   34290    Python         20.2 MB    3h 12m     abhishek
  5237    Code Helper (Plugin)   34073    Python         20.1 MB    3h 12m     abhishek
  5932    Code Helper (Plugin)   61773    Python         57.5 MB    58m 35s    abhishek
  42050   OneDrive Sync Serv..   36643    Unknown        14.4 MB    3d 1h      abhishek

Fix port conflicts and auto-restart

$ portctl fix 3000 --run "npm run dev"

  ✔ Killed safely  port 3000 is now free
  🚀 Restarting: npm run dev

One command. Port cleared, dev server restarted.

Diagnose dev environment issues

$ portctl doctor

  🩺 2 issues found

  1. Idle process Code Helper (PID 34290) at 0.0% CPU [auto-fixable]
     → Idle Code Helper on port 3898 -- consider killing to free resources
  2. Idle process Code Helper (PID 34073) at 0.0% CPU [auto-fixable]
     → Idle Code Helper on port 5237 -- consider killing to free resources

  ⚙ Run portctl doctor -y to auto-fix 2 issues

Group ports by service role

$ portctl group --dev

  ⚡ 4 active ports in 2 groups

  ⚙ Frontend (2)
  ────────────────────────────────────────────────────────────────────────────
  3000    node                   81106    Next.js        42.9 MB    7m 21s     abhishek
  3898    Code Helper (Plugin)   34290    Python         20.2 MB    3h 12m     abhishek

  ⚙ Backend (2)
  ────────────────────────────────────────────────────────────────────────────
  5237    Code Helper (Plugin)   34073    Python         20.0 MB    3h 12m     abhishek
  5932    Code Helper (Plugin)   61773    Python         57.5 MB    58m 39s    abhishek

Interactive terminal UI

$ portctl ui

Arrow keys to navigate, enter to inspect, f to fix. Full interactive TUI for port management, powered by ratatui.

Define your dev stack with .portctl.toml

$ portctl init

   Created .portctl.toml
   Detected Next.js project (port 3050)
   Port 3050 found in package.json scripts

portctl reads your package.json scripts and detects hardcoded ports (--port 3050, -p 8080, etc.). In a monorepo, it scans subdirectories and generates a multi-service config automatically.

Edit the generated config to declare your services:

[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

[services.worker]
port = 9090
run = "python worker.py"
env = { PYTHON_ENV = "development" }

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

Start your entire dev stack

$ portctl up

  🚀 Starting 3 services...

  ✔ api started on port 8080
  ✔ frontend started on port 3050
  ✔ worker started on port 9090

  ✔ 3/3 services started.

Pre-flight checks run automatically. If a port is busy, portctl tells you. Add -y to auto-fix conflicts before starting.

portctl tracks spawned PIDs in .portctl.pids. If a framework binds a different port than configured (e.g., Next.js auto-increments when a port is taken), portctl detects the actual port and reports it:

  ✔ frontend started on port 3001 (configured: 3000)
      ⚠ port 3000 was busy, update .portctl.toml to match

Stop everything

$ portctl down

  🛑 Stopping 3 services...

  ✔ api stopped (port 8080)
  ✔ frontend stopped (port 3050)
  ✔ worker stopped (port 9090)

  ✔ 3/3 services stopped.

down uses a 3-tier strategy to find and stop processes: checks the declared port, then the actual port from .portctl.pids (if the process moved), then kills by saved PID directly.

Pre-flight port check

$ portctl preflight 3000 8080 5432

  🔍 Pre-flight check for 3 ports...

  ✔ Port 3000 is free
  ✔ Port 8080 is free
  ✘ Port 5432 is busy -- postgres (PID 1234, PostgreSQL)

  ⚠ 1 port is already in use. Run portctl fix <port> to fix.

Run without arguments to check all ports from .portctl.toml.

Watch a port and auto-restart on crash

$ portctl watch 3000

  👀 Watching port 3000 (every 2s, Ctrl-C to stop)

  ✔ Port 3000 is up -- Next.js (PID 81106)
  ✘ Port 3000 went down -- Unknown crash reason (was PID 81106)
  🚀 Auto-restarting: npm run dev
  ✔ Port 3000 recovered (PID 82001, Next.js) -- downtime: 3s

If a .portctl.toml defines a run command for the watched port, portctl auto-restarts it when it crashes.

Validate port assignments

$ portctl registry check

  🔍 Checking port registry...

  ✔ No port conflicts found across 3 services.

Detects duplicate ports across services and profile overrides in .portctl.toml.

Run all checks in CI

$ portctl ci

  ▶ Step 1/4: Validate config... ✔
  ▶ Step 2/4: Registry check... ✔
  ▶ Step 3/4: Pre-flight check... ✔
  ▶ Step 4/4: Doctor... ✔

  ✔ All checks passed.

Non-interactive runner for CI/CD pipelines. Runs config validation, registry check, preflight, and doctor in sequence. Exits 1 on failure. Supports --json.

Switch profiles

$ portctl use staging

  ✔ Switched to profile: staging
  → frontend port: 3100
  → api port: 8180

Switch between named profiles defined in .portctl.toml. The active profile is persisted to .portctl.state and applied automatically to up, down, watch, and preflight.


CLI Commands Reference

Command Description Example
portctl scan List all listening ports with service, memory, uptime portctl scan
portctl <port> Inspect a single port in detail portctl 3000
portctl fix <ports> Safely kill the process on one or more ports portctl fix 3000 8080
portctl fix <ports> --run Kill and auto-restart a dev server portctl fix 3000 --run "npm run dev"
portctl fix <ports> -y Skip confirmation prompt portctl fix 3000 8080 -y
portctl kill <ports> Direct kill with safety confirmation portctl kill 3000 8080
portctl group Ports organized by role (frontend/backend/db/infra) portctl group --dev
portctl doctor Find stale servers, idle processes, conflicts portctl doctor
portctl doctor -y Auto-fix all safe issues portctl doctor -y
portctl history View past actions with timestamps portctl history
portctl history --stats Kill stats: success rate, top ports, top processes portctl history --stats
portctl project Detect project type, suggest dev commands portctl project
portctl ui Interactive TUI with keyboard navigation portctl ui
portctl init Create a .portctl.toml (auto-detects ports) portctl init
portctl up Start all services from .portctl.toml portctl up
portctl up -y Start services, auto-fix port conflicts first portctl up -y
portctl down Stop all services from .portctl.toml portctl down
portctl preflight Check if ports are free before starting portctl preflight 3000 8080
portctl watch <port> Monitor a port, alert on crash, auto-restart portctl watch 3000
portctl registry check Validate port assignments for conflicts portctl registry check
portctl ci Run all checks non-interactively (CI/CD mode) portctl ci --json
portctl use <profile> Switch to a named profile from .portctl.toml portctl use staging

All commands support --json for scripting and CI pipelines.


Why portctl Over kill-port, fkill, or lsof

It's not kill -9 with extra steps.

portctl is a process classification engine built for developer productivity:

  • Identifies services -- Next.js, Vite, Django, Flask, Express, PostgreSQL, Redis, Docker, and 13+ categories with confidence scores
  • Safety system -- blocks system-critical processes (PID 1, sshd, launchd), warns about databases (data loss risk), approves dev servers
  • Graceful shutdown -- SIGTERM first, waits for clean exit, escalates to SIGKILL only if needed
  • Project-aware -- reads package.json, Cargo.toml, pyproject.toml to suggest the right restart command
  • Docker-aware -- detects container ports vs host ports
  • History -- every action logged to ~/.portctl/history.json with timestamps and outcomes

How portctl works under the hood

  1. Scan -- queries the OS for all listening ports, resolves PIDs via sysinfo
  2. Classify -- identifies the service type (Next.js, PostgreSQL, Docker, etc.)
  3. Assess -- safety check: SAFE / WARN / BLOCK
  4. Strategy -- picks the right approach: Graceful, Escalating, or Force
  5. Execute -- sends signals, waits for exit, verifies the port is free
  6. Recover -- detects the project, suggests restart, or auto-restarts with --run

Safety tiers

Verdict Examples Behavior
BLOCKED PID 0/1, launchd, systemd, sshd, kernel_task Refuses to kill
WARNING PostgreSQL, MySQL, Redis, Docker, Nginx Warns about consequences, asks for confirmation
SAFE Next.js, Vite, Create React App, Django dev, Flask, Node.js Kills gracefully

Developer Productivity Workflows

"Port 3000 is already in use" after a crash

Your Next.js server crashed. The port is stuck. You just want to get back to coding.

portctl fix 3000 -y --run "npm run dev"

Port cleared, server restarted. One line.

Make npm run dev crash-proof

Add portctl to your scripts so port conflicts resolve themselves:

{
  "scripts": {
    "dev": "portctl fix 3000 -y --run 'next dev'",
    "dev:api": "portctl fix 8080 -y --run 'node server.js'",
    "dev:clean": "portctl doctor -y && npm run dev"
  }
}

Now npm run dev works every time, even if something is already on port 3000.

Morning dev environment reset

You open your laptop. Stale servers from yesterday are hogging ports and memory.

portctl doctor -y

Finds zombie processes, idle servers, and cleans them up automatically.

"What is using port 8080?"

Something is squatting on your API port but you have no idea what.

portctl 8080

Shows the process name, PID, service type, memory, uptime, project directory, and whether it's safe to kill.

Full-stack dev with .portctl.toml

Frontend on 3000, API on 8080, worker on 9090. Define them once, manage them forever:

# Initialize config
portctl init

# Start everything (runs preflight checks automatically)
portctl up

# Stop everything at end of day
portctl down

No more opening 5 terminals and running commands manually.

Monitor a flaky dev server

Your dev server keeps crashing. Let portctl watch it and auto-restart:

portctl watch 3000

If a .portctl.toml defines a run command for port 3000, portctl auto-restarts it when it goes down.

Pre-flight check before deployment scripts

# Check all ports from .portctl.toml are free
portctl preflight

# Check specific ports
portctl preflight 3000 8080 5432

Shell aliases for daily use

# ~/.zshrc or ~/.bashrc
alias pf='portctl fix'
alias pfs='portctl scan'
alias pfd='portctl doctor -y'
alias pu='portctl up'
alias pd='portctl down'
alias dev3='portctl fix 3000 -y --run "npm run dev"'
alias dev8='portctl fix 8080 -y --run "node server.js"'

Fix multiple ports at once

Clearing out a full dev environment before starting fresh:

# Fix multiple ports in one command
portctl fix 3000 8080 5173 -y

# Or with .portctl.toml
portctl down && portctl up

CI / pre-commit: ensure clean ports

# Run all checks in one command (exits 1 on failure)
portctl ci

# Or with JSON output for CI parsing
portctl ci --json

Validate port assignments before deploying

# Check for duplicate ports across services and profiles
portctl registry check

Switch between dev and staging

# Define profiles in .portctl.toml, then switch:
portctl use staging
portctl up

# Switch back to default
portctl use default
portctl up

Pipe to scripts with JSON output

# Get all listening ports as JSON
portctl scan --json | jq '.[] | select(.service == "Next.js")'

# Count active dev servers
portctl scan --json | jq '[.[] | select(.service != "Unknown")] | length'

Comparison: portctl vs kill-port vs fkill

kill-port fkill portctl
Service identification No Name only Full (service, memory, uptime, CWD)
Safety checks No No Yes (safe / warn / block)
Graceful shutdown No No Yes (SIGTERM, then escalate)
Restart hints No No Yes (project-aware)
Auto-restart No No Yes (--run)
Docker awareness No No Yes
Auto-diagnosis No No Yes (doctor)
Port grouping No No Yes (by role)
Action history No No Yes
Interactive TUI No Yes Yes
Project config file No No Yes (.portctl.toml)
Dev stack up/down No No Yes (up / down)
Port monitoring No No Yes (watch)
Pre-flight checks No No Yes (preflight)
Crash detection No No Yes (signal, OOM, zombie)
Port registry No No Yes (conflict detection)
CI/CD mode No No Yes (ci command)
Profiles No No Yes (use for dev/staging/prod)
Platform Node.js Node.js Native binary
Size ~50MB ~50MB ~1.2MB

Architecture & Performance

src/
  scanner/      Batch port scanning with sysinfo process resolution
  classifier/   13+ service classifiers with confidence scoring
  engine/       Safety checks, strategy selection, graceful kill with retry
  platform/     macOS (lsof + libc) / Linux (/proc/net/tcp) / Windows (netstat)
  project/      Filesystem project detection (package.json, Cargo.toml, etc.)
  docker/       Container awareness via docker ps
  grouping/     Port role classification (frontend/backend/database/infra)
  doctor/       Stale servers, idle processes, crowded ports
  history/      Action log persisted to ~/.portctl/history.json
  config/       .portctl.toml project config loader (monorepo, port detection)
  watch/        Continuous port monitoring with crash detection
  stack/        Dev stack orchestration (up/down with PID tracking)
  preflight/    Pre-flight port availability checks
  crash/        Crash reason detection (signal, OOM, zombie)
  registry/     Port conflict detection across services and profiles
  ci/           Non-interactive CI/CD runner (config + registry + preflight + doctor)
  plugin/       Extensible ServiceDetector trait for custom detectors
  cli/          Clap v4 + colored output + ratatui TUI

Built in Rust for speed and reliability. Ships as a single ~1.2MB static binary with zero runtime dependencies. No Node.js, no Python -- just a fast native CLI tool for managing ports and debugging dev environments.


Contributing

git clone https://github.com/abhishekayu/portctl.git
cd portctl
cargo build
cargo test
  • Report bugs or request features via Issues
  • Add new service classifiers
  • Improve platform support
  • Write new doctor diagnostics

See CONTRIBUTING.md for details.


License

MIT -- free for personal and commercial use.