Why I Rewrote kill-port in Rust

The engineering story behind portrm: service detection, a 3-tier safety system, and a 1.2MB binary with zero dependencies.

10 min read · April 2026

The frustration every dev knows

It always happens at the worst time. You sit down to work, run npm run dev, and:

Error: listen EADDRINUSE :::3000

Something is already on port 3000. You don't know what. You don't know if it's important. You just know your dev server won't start.

So you do the ritual. You open a new tab. You type lsof -i :3000. You squint at a wall of columns trying to find the PID. You copy it. You type kill -9 84921. You hold your breath and hope it wasn't PostgreSQL.

I did this hundreds of times. Every single day. Sometimes multiple times a day when switching between projects. And every time I thought: why is this still a multi-step, error-prone process in 2026?

So I started looking at existing tools. There were two popular ones: kill-port and fkill. And they both had the same fundamental problem.

The problem with kill-port and fkill

kill-port does exactly what the name says. You give it a port, it kills whatever is there. No questions asked. No context about what it's killing. No safety net. It's kill -9 with a friendlier API.

Here's what happens when you run kill-port 5432:

$ npx kill-port 5432
Process on port 5432 killed

That was PostgreSQL. Your database. With active connections. No warning, no confirmation. Just gone.

fkill is better about UX. It has a nice interactive mode and can search by process name. But it still doesn't understand what services are running. It can't tell you "hey, that's your production database, maybe don't nuke it."

Both tools also share an architectural problem: they're Node.js packages. That means:

I wanted something that actually understood my dev environment. Something that knew the difference between a Next.js dev server I can safely restart and a PostgreSQL instance I definitely should not touch. Something tiny, instant, and dependency-free.

So I built portrm.

Why Rust

I didn't start with Rust because it was trendy. I started with it because the constraints demanded it.

A port manager runs in the critical path of your workflow. You're already frustrated when you reach for it. The last thing you want is to wait for a tool to boot up. It needs to be instant.

Rust gave me three things no other language could:

1. Binary size. portrm compiles to a ~1.2MB static binary. No runtime. No interpreter. No node_modules. Just a single file you drop on your PATH. Compare that to kill-port's ~50MB installed footprint.

2. Startup time. portrm starts in under 5ms. There's no VM to warm up, no JIT to kick in. You type a command, you get output. The bottleneck is your OS APIs, not the tool.

3. Zero dependencies at runtime. The binary links everything statically. It doesn't need Node.js, Python, or any shared libraries. It works on a fresh machine, in a Docker container, or in CI with nothing else installed.

Rust also gave me something less tangible but equally important: confidence in correctness. When you're writing a tool that kills processes, you really don't want use-after-free bugs or race conditions. Rust's ownership model makes entire classes of concurrency bugs impossible at compile time.

What portrm does differently

portrm isn't just kill -9 with a prettier name. It actually understands what's running on your machine.

Service identification

When you run ptrm scan, portrm doesn't just list PIDs. It classifies every listening process into one of 13+ service categories: Node.js, Python, Rust, Java, Docker, PostgreSQL, MySQL, Redis, Nginx, and more. It does this by analyzing the process binary, command-line arguments, and listening patterns.

$ ptrm scan

  ⚡ 4 active ports

  PORT   SERVICE      PID     SAFETY    MEM       UP
  ─────────────────────────────────────────────────
  3000   Next.js      59480   SAFE      14 MB     2h
  8080   Express      61023   SAFE      22 MB     1h
  5432   PostgreSQL   1204    WARNING   58 MB     14d
  6379   Redis        1312    WARNING   12 MB     14d

3-tier safety system

This is the feature that made me build portrm in the first place. Every process gets classified into one of three safety tiers:

When you run ptrm fix 5432, portrm doesn't just kill it. It tells you:

$ ptrm fix 5432

  ⚠ PostgreSQL (PID 1204) - WARNING tier
    This is a database with potential active connections.
    Killing it may cause data loss.
    Continue? (y/N)

Compare that to npx kill-port 5432 which just silently kills it and prints "done."

Graceful shutdown

portrm sends SIGTERM first, waits for a configurable timeout, and only escalates to SIGKILL if the process doesn't respond. Most dev servers handle SIGTERM gracefully, flushing buffers and closing connections properly. kill -9 gives them no chance to clean up.

Project configuration

Drop a .ptrm.toml in your repo and your whole team gets the same port setup:

[project]
name = "my-app"

[services.frontend]
port = 3000
run  = "npm run dev"

[services.api]
port = 8080
run  = "cargo run"

[services.db]
port = 5432
run  = "pg_ctl start"

Then ptrm up starts everything. ptrm down stops everything. ptrm preflight checks all your ports are free before you start. ptrm doctor diagnoses what went wrong when something crashes.

Real benchmarks

Numbers matter. Here's how portrm compares to the alternatives on real hardware (M1 MacBook Pro):

Metric portrm kill-port fkill
Startup time <5ms ~200ms ~180ms
Install size ~1.2MB ~48MB ~52MB
Runtime deps 0 Node.js Node.js
Service detection 13+ categories
Safety tiers SAFE / WARNING / BLOCKED

The startup time difference is the one you feel. 5ms vs 200ms might not seem like much, but when you're running ptrm scan or ptrm fix multiple times in a session, it adds up. The tool feels instant in a way Node.js tools simply can't.

What's next

portrm is at v2.2.0 now and the core is solid. But there's more I want to build:

Try it yourself

portrm installs in 5 seconds, takes 1.2MB, and doesn't need Node.js:

$ npm i -g portrm          # npm
$ brew install abhishekayu/tap/portrm  # Homebrew
$ cargo install portrm      # Cargo
$ pip install portrm        # pip
$ npx portrm scan           # try without installing

Then run ptrm scan and see what's actually running on your ports. You might be surprised.

If you've ever lost work to a blind kill -9, or if you're tired of the four-step lsof ritual, give portrm a shot. It's open source, MIT licensed, and I use it every single day.

GitHub · portrm.dev · npm · crates.io