You know the feeling. ps aux scrolls past. Something is listening on port 5000 and you have no idea what put it there. You start the ritual: lsof -i :5000, then ps -p <pid>, then systemctl status, then docker ps, stitching the story together in your head. witr does that stitching for you.

What it is

witr — short for "Why Is This Running?" — is a single-binary CLI (with a TUI) that traces any process, port, container, or file back to the exact chain that started it. Point it at a name, a PID, a port, or a container, and it hands you a causal ancestry chain: systemd → pm2 → node, the working directory, the git branch, the bound sockets. One command instead of five.

The tools we already have — ps, top, lsof, ss, systemctl, docker ps — are great at showing what is running. They leave the why to you. witr makes causality explicit.

Why it's worth your time

The value isn't a prettier ps. It's the ancestry chain. When a process shows up and you don't recognize it, the real question is never "what is this" — it's "who started this, and what's keeping it alive." That answer is normally spread across a supervisor, maybe a container runtime, maybe a stray SSH session, maybe cron. witr collapses all of that into one narrative.

For anyone running a self-hosted box with a dozen services layered through systemd, Docker, and PM2, this is the tool you didn't know you were manually emulating every time something misbehaved.

Hands on

Install is a single binary, and it's already packaged nearly everywhere. On Debian sid / Ubuntu 26.04+ (and Kali, Devuan, Raspbian) it's in the official repos:

sudo apt install witr

Homebrew, on macOS or Linux:

brew install witr

Or the install script for the freshest release:

curl -fsSL https://raw.githubusercontent.com/pranshuparmar/witr/main/install.sh | bash

(That last one pipes a script to bash — read it first if that makes you nervous. It should.)

Now the good part. Ask it about a name:

witr node
Target      : node
Process     : node (pid 14233)
User        : pm2
Command     : node index.js
Started     : 2 days ago (Mon 2025-02-02 11:42:10 +05:30)

Why It Exists :
  systemd (pid 1) → pm2 (pid 5034) → node (pid 14233)

Source      : pm2
Working Dir : /opt/apps/expense-manager
Git Repo    : expense-manager (main)
Sockets     : 127.0.0.1:5001 (TCP | LISTENING)

That "Why It Exists" line is the whole pitch. It read the supervisor, walked the chain, and told you PM2 is keeping this alive — plus which repo and branch it's running from. That last detail alone has saved me a cd and a git status.

Chase a port instead, ancestry only:

witr --port 5000 --short
systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → python (pid 1482060)

Or get the full tree, including children:

witr --pid 143895 --tree

Every target flag — --pid, --port, --file, --container — is repeatable and mixable. --container searches across Docker, Podman, nerdctl, k8s/crictl, Incus, LXC, LXD, and FreeBSD jails, so witr --container redis finds it regardless of runtime.

Two things worth knowing. First, --json gives you machine-readable output, and witr returns real exit codes (0 clean, 1 warnings, 2 not found, 3 permission denied) — which means it slots into scripts and CI, not just interactive poking. Second, run witr with no arguments and you get the TUI: live tabs for processes, ports, containers, and file locks, with an ancestry side panel and the ability to send signals right from the UI.

The warnings deserve a mention too — it flags things like a process listening on 0.0.0.0, running as root, holding dangerous capabilities, or showing LD_PRELOAD injection indicators. Not a security scanner, but a useful nudge.

Honest verdict

I like this one. It's not solving an impossible problem — everything witr reports, you could dig out by hand. But "could" is doing a lot of work in that sentence, and the whole point is that you stop doing it by hand. The ancestry chain is genuinely the right abstraction for the question, and the tool is honest about uncertainty rather than pretending it always knows.

The caveats are inherent, not sloppy: process ancestry is a best-effort, real-time walk, so very short-lived processes can vanish before witr catches them. Detection is "best effort with explicit uncertainty" by design — it won't lie to you, but it also won't always have a clean answer. And realistically, some of the deeper output needs root to see everything (that 3 exit code exists for a reason).

Who's it for? Anyone who administers a box with more than a couple of moving parts. If your whole world is one Docker Compose file, you'll reach for it occasionally. If you're running a real self-hosted stack, it earns a spot in your PATH.

Go try it

Install it, then just run witr with no arguments and poke around the TUI for two minutes. After that, next time something's on a port you don't recognize, type witr --port <n> and watch it tell you the whole story. That's the moment it clicks.

Project: github.com/pranshuparmar/witr — and there's a browser sandbox if you want to try it before installing anything.