Tools: I’ve Been Running 2–3 Commands Just to Check a Port for Years. I Finally Got Tired of It.

Tools: I’ve Been Running 2–3 Commands Just to Check a Port for Years. I Finally Got Tired of It.

The Idea

Human Readability Was the Whole Point

Under the Hood

What I Didn't Expect: The Community

The TUI Changed How I Use It

The Problem Is Still Underappreciated Something's listening on port 3000. You need to know what. Okay, you have a PID now. But you don't know much else. What started it? Is it a service? A container? Something someone kicked off with nohup and forgot about? So you keep going: A truncated command string. A username. Not much help. Maybe systemctl? Nothing, it's not a systemd service. Try Docker? Not a container either. At this point you're three commands deep and you still don't really know why this thing is running. You just know it exists. If this sounds familiar, you've been doing what I've been doing for years. Manually stitching together the output of lsof, ps, ss, systemctl, docker ps, and pstree. Not because any of them are bad tools, but because none of them answer the actual question. They tell you what is running. They don't tell you why. Who started this process? What's keeping it alive? What's the chain of responsibility from PID 1 down to the thing I'm looking at? That information exists on your system, it's just scattered across a dozen places that no single tool brings together. I've done this dance hundreds of times. On production servers during incidents, on dev machines when something's hogging a port, on staging boxes where nobody remembers what half the processes are for. I've always built small utilities for myself. Little scripts and tools that scratch personal itches, nothing fancy, nothing public. But at some point I started thinking about whether I could build something that wasn't just useful for me. The way I approached it was simple: what's something I do repeatedly that's frustrating, and that's probably a generic problem? Not something niche to my setup, but something that anyone who spends time in a terminal deals with. And that's when it hit me. I'd been manually stitching together Unix command output for years. Everyone has been. The raw commands are powerful, that's not the issue. The issue is that their output feels like it was made for machines, not humans. You get fragments of information from five different tools and your brain has to assemble the picture. The more I thought about it, the more compelling it felt. What if there was a single command that could tell you everything about a running process, what it is, where it came from, what started it, what it's listening on, all in one human-readable output? That's how witr (Why Is This Running?) was born. From the very beginning, I fixated on one thing: the output had to be human first. Not JSON-by-default that you pipe through jq. Not a wall of fields you have to squint at. Something you glance at and immediately understand. That Why It Exists line is the entire reason this tool exists. One line, and you know the full ancestry: systemd started pm2, pm2 started this node process, it's running from the /tmp/hello directory, listening on port 5000. No stitching. No cross-referencing. Just the answer. The --port flag ended up being the one I personally use the most. Is something using port 5000? Is a port free? One command: Done. That used to be a lsof followed by a ps followed by maybe a pm2 list to figure out. Now it's one line. As I kept thinking about what the tool should cover, more output modes made sense. --tree for when you want to see the full process ancestry and children visually. --short for when you just need the chain. --json for when you're piping into something else. --env for environment variables. Each one came from asking "what's the next thing I'd have to run a separate command for?" The core idea is simple: everything eventually becomes a process question. Ports, services, containers, they all map to PIDs somewhere. Once witr identifies the target PID, it walks the parent chain all the way up to PID 1 and gathers context along the way. On Linux this mostly comes from /proc: parent relationships, working directory, environment, command arguments, open sockets. That information is then enriched with signals from other sources like systemd units, container runtimes, and Git repositories in the working directory. The result is not just a snapshot of the process, but the causal chain that explains how it came to exist. I’ll go deeper into the architecture in a follow-up post. I originally built witr with only Linux in mind. I had a vague plan to eventually add macOS support, but it wasn't a priority. What actually happened was different. The response was overwhelming, far beyond what I expected from a process utility. witr crossed 10,000 stars within the first 10 days. People didn't just file issues, they showed up with pull requests. macOS support happened much faster than I planned because the community wanted it and helped build it. FreeBSD and Windows support dropped soon after. Features I hadn't even considered started appearing: the --verbose flag, the --file flag, container detection, Nix support. Bugs I hadn't caught got found and fixed. witr is now available across a variety of package managers, most of which are community maintained. It's already in Debian's apt unstable channel. It's been featured in blog posts, dev newsletters, YouTube videos, and even Instagram and TikTok content, which is not something I ever imagined. What started as a personal passion project quickly started looking like a proper community tool, and I'm genuinely thankful for the people who showed up in those early weeks with feedback, code, and enthusiasm. That support made all the difference. In v0.3.0 I added an interactive TUI mode. Just run witr with no arguments or witr -i. It gives you a live, sortable process list. You can click into any process (yes, mouse support) and see its full ancestry, children, environment variables, working directory, everything. There's a port view that lets you browse open ports and immediately trace what's behind each one. You can send signals directly from it too: kill, terminate, pause, resume, renice. It has honestly changed how I use the tool day to day, and I'm really happy with how it turned out. We have incredible observability tooling now. Metrics, logs, traces, APM dashboards that can tell you the p99 latency of any endpoint across a fleet of servers. But ask "why is this specific process running on this specific machine" and most people are still doing exactly what I was doing: running lsof, then ps, then systemctl, then docker ps, then staring at a pstree output trying to connect the dots. The gap between "what is running" and "why is it running" is small in theory but enormous in practice. It's the difference between seeing a process and understanding it. If you've ever spent 20 minutes tracing a mystery port, or found a rogue process that's been running since someone left the company, or just gotten tired of running the same 3 commands every time something looks wrong, witr might save you some time. It's a single static binary. It works on Linux, macOS, FreeBSD, and Windows. Though I'll be honest, I still mostly use it on Linux servers myself. If you end up trying it, I'd love to hear what you think. And if you have a process debugging war story, the kind where you spent way too long figuring out something that should have been obvious, I'd love to hear that too. We've all been there. witr is open source and free. If it saves you a few commands, consider starring the repo. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ lsof -i :3000 lsof -i :3000 lsof -i :3000 ps aux | grep 14233 ps aux | grep 14233 ps aux | grep 14233 -weight: 500;">systemctl -weight: 500;">status 14233 -weight: 500;">systemctl -weight: 500;">status 14233 -weight: 500;">systemctl -weight: 500;">status 14233 witr --port 5000 witr --port 5000 witr --port 5000 Target : node /tmp/hello Process : node /tmp/hello (pid 1898657) {forked} User : translytics Command : node /tmp/hello/index.js Started : 29 min ago (Thu 2026-03-12 12:58:34 +01:00) Why It Exists : systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → node /tmp/hello (pid 1898657) Source : systemd Description : Session 31906 of User root Unit File : /run/systemd/transient/session-31906.scope Working Dir : /tmp/hello Listening : 0.0.0.0:5000 Warnings : • Process is listening on a public interface Target : node /tmp/hello Process : node /tmp/hello (pid 1898657) {forked} User : translytics Command : node /tmp/hello/index.js Started : 29 min ago (Thu 2026-03-12 12:58:34 +01:00) Why It Exists : systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → node /tmp/hello (pid 1898657) Source : systemd Description : Session 31906 of User root Unit File : /run/systemd/transient/session-31906.scope Working Dir : /tmp/hello Listening : 0.0.0.0:5000 Warnings : • Process is listening on a public interface Target : node /tmp/hello Process : node /tmp/hello (pid 1898657) {forked} User : translytics Command : node /tmp/hello/index.js Started : 29 min ago (Thu 2026-03-12 12:58:34 +01:00) Why It Exists : systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → node /tmp/hello (pid 1898657) Source : systemd Description : Session 31906 of User root Unit File : /run/systemd/transient/session-31906.scope Working Dir : /tmp/hello Listening : 0.0.0.0:5000 Warnings : • Process is listening on a public interface witr --port 5000 --short witr --port 5000 --short witr --port 5000 --short systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → node /tmp/hello (pid 1898657) systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → node /tmp/hello (pid 1898657) systemd (pid 1) → PM2 v5.3.1: God (pid 1481580) → node /tmp/hello (pid 1898657) -weight: 500;">brew -weight: 500;">install witr -weight: 500;">brew -weight: 500;">install witr -weight: 500;">brew -weight: 500;">install witr