Why portop
The everyday basics of seeing what's on your ports — done once, done well, with the context ss and lsof leave out.
●Live, color-coded table
LISTEN, ESTABLISHED and transient states colored at a glance, with per-process CPU% sampled like top.
✕Kill, safely
k then confirm — graceful SIGTERM or forced SIGKILL. Never a stray keypress away.
{}Service & Docker aware
Every row shows the owning systemd .service unit (Linux) or Windows service, and the Docker container behind it — no extra daemons.
●New-port alerts
Freshly opened listening ports are highlighted live, with an optional desktop notification.
±Baseline drift detection
--save-baseline then --diff from a cron job — exit code 3 the moment something unexpected starts listening.
↔Compose port audit
--compose DIR checks a Docker Compose folder and reports expected, unused or conflicting published ports.
◈Web dashboard
portop --web shows socket and process details in the browser, with light and dark themes and confirmed SIGTERM/SIGKILL. Add --web-auth for a sign-in page.
◆Cockpit add-on
The same view under Tools → Ports in Cockpit on Linux. Signal a process after confirmation, using your Cockpit login and permissions.
◆Live settings screen
, opens it: cycle 13 themes (Dracula, Nord, Solarized, Gruvbox, Catppuccin, Tokyo Night, Monokai, Darcula, VS Code, Ubuntu, Rosé Pine, ...) with the arrow keys as the whole UI re-skins live, rebind any of 20 actions — saved automatically, no file to touch.
Beyond wrapping ss and lsof
Neither tells you this on its own — portop does, from the same dashboard.
Save a snapshot of what's listening, then diff against it later — from a cron job or systemd timer. Exit code 3 the instant a port you didn't expect shows up.
systemd unit and Docker container, resolved straight from the process's cgroup — no D-Bus, no Docker SDK, no daemon of its own.
Point it at a Compose folder to see whether configured published ports are actually listening, unused, or owned by another process/container. With Docker access, it reads Compose labels so docker-proxy can still be tied back to the service.
Can't resolve a row owned by root (docker-proxy, systemd-resolved)? portop tells you exactly why and that sudo would fix it, instead of a silent blank.
Columns drop themselves — least useful first — as the terminal narrows, so rows never wrap into a mess. Works down to 80 columns.
Parsed straight from /etc/services: :22 shows as ssh, :443 as https, right in the table.
A clean JSON snapshot for piping into jq, a dashboard, or your own monitoring — same data the TUI shows, no scraping.
In action
Filtering to a port, checking who owns it, and the confirm-before-kill flow — a real recorded session, not staged.
Beyond the terminal
The same live view of ports and processes, for when a terminal isn't at hand: in a browser, or inside Cockpit.
portop --web serves a dashboard on 127.0.0.1:8088, with search, process details and confirmed SIGTERM/SIGKILL. Add --web-auth for a sign-in page; with it, the dashboard can listen beyond loopback and serve HTTPS with --web-tls-cert/--web-tls-key.
On Linux, copy cockpit/portop to ~/.local/share/cockpit/portop and open Tools → Ports (portop). It runs through Cockpit's bridge with your login and permissions, so there's no extra service to expose. Setup steps.
Installation
Linux: x86-64, ARM64, ARMv7 and ARMv6. macOS: Intel and Apple Silicon. Windows: x64 and ARM64, via winget, the Microsoft Store or a zip. All release targets and limitations are listed below.
| System | Architecture | Target | Formats | Requirements |
|---|---|---|---|---|
| Linux | Intel/AMD 64-bit | linux_amd64 | .tar.gz, .deb, .rpm | 64-bit x86 OS; amd64 baseline (v1). |
| Linux | ARM64 / AArch64 (64-bit) | linux_arm64 | .tar.gz, .deb, .rpm | 64-bit ARM OS; ARMv8.0 baseline. |
| Linux | ARMv7 (32-bit) | linux_armv7 | .tar.gz, .deb, .rpm | 32-bit ARM Linux with hardware floating point (VFPv3). |
| Linux | ARMv6 (32-bit) | linux_armv6 | .tar.gz, .deb, .rpm | 32-bit ARM Linux with hardware floating point (VFP); includes the CPU target used by original Pi 1 / Zero. |
| macOS | Intel (64-bit) | darwin_amd64 | .tar.gz | 64-bit Intel Mac; no 32-bit macOS build. |
| macOS | Apple Silicon (64-bit) | darwin_arm64 | .tar.gz | Native ARM64 Mac; no Rosetta required. |
| Windows | Intel/AMD 64-bit | windows_amd64 | .zip, winget, Microsoft Store | Windows 10 1809 or later. |
| Windows | ARM64 | windows_arm64 | .zip, winget, Microsoft Store | Windows 11 on ARM. |
Archive names: portop_<version>_<target>.tar.gz (.zip on Windows). Linux packages use the same target suffix with .deb or .rpm. Replace the example's linux_amd64 with your target.
Compatibility and limitations
- Choose for your OS: a 64-bit ARM board with a 32-bit OS needs
armv7, orarmv6for older CPUs. ARM64 requires a 64-bit OS. - ARM32: hardware floating point is required (VFP for ARMv6, VFPv3 for ARMv7). ARMv5, soft-float
armel, big-endian ARM, 32-bit x86, 32-bit Windows and unlisted targets are not supported by these releases. - Packages: ARMv6 and ARMv7 DEBs both use
armhfmetadata; choose by filename and install only one variant. ARMv6 distribution support varies. RPM labels arex86_64,aarch64,armv6hlandarmv7hl. If your RPM manager does not accept the architecture, use the matching archive rather than forcing installation. - OS versions: prebuilt binaries need no Go or C runtime installation. An old Linux image may still have a kernel too old for the runtime. Check Go's OS requirements; CPU support is not a promise that every historical OS version works.
- Linux: readable
/proc/netis required. Containers show their own network namespace. Root may be needed to identify other users' processes. Systemd and Docker enrichment depend on accessible services and metadata. - Docker Compose audit: fixed
ports:entries are checked against host listeners. Docker access lets portop confirm the Compose service behind published ports anddocker-proxy; without it, a listening port can still be reported but the expected service may be unknown. - macOS: uses system
lsofandps; scans may be slower. Some sockets are omitted withoutsudo. Systemd, Docker Desktop VM process metadata and thread counts are unavailable. Non-notarized binaries may need first-launch approval: see the macOS tab. - Windows: sockets and their owning PID come from the IP Helper API, with no elevation needed. The SYSTEMD column becomes SERVICE (the Windows services hosted by the process). Docker Desktop containers are matched by published port through the
dockerCLI. Details of other users' or elevated processes need an Administrator terminal; both kill choices end the process. - Validation: build/emulation checks do not replace physical ARM or native Mac testing. See the validation report for the checks performed.
Detects all six Linux and macOS targets above. On Linux ARM64 kernels, getconf LONG_BIT distinguishes a 32-bit userland; if unavailable, detection uses the kernel architecture. For custom setups, download the script and override detection:
curl -fsSL https://raw.githubusercontent.com/padovanl/portop/main/install.sh -o install.sh PORTOP_ARCH=armv7 sh install.sh
Overrides: amd64, arm64, armv6, armv7. macOS accepts amd64 and arm64 only.
# detects arch, verifies the release checksum
curl -fsSL https://raw.githubusercontent.com/padovanl/portop/main/install.sh | sh
# replace <version> with the latest release number, no leading "v"
curl -fLO https://github.com/padovanl/portop/releases/latest/download/portop_<version>_linux_amd64.deb
sudo dpkg -i portop_<version>_linux_amd64.deb
# download the .rpm for your architecture from Releases sudo dnf install ./portop_<version>_linux_amd64.rpm # openSUSE: sudo zypper install ./portop_<version>_linux_amd64.rpm
Apple Silicon and Intel: the installer selects the matching archive and verifies its checksum.
# automatic installation
curl -fsSL https://raw.githubusercontent.com/padovanl/portop/main/install.sh | sh
portop
Prefer a manual download?
Use darwin_arm64 for Apple Silicon or darwin_amd64 for Intel. Replace <version> with the release number.
archive="portop_<version>_darwin_arm64.tar.gz" # Intel: darwin_amd64
curl -fLO "https://github.com/padovanl/portop/releases/latest/download/$archive"
tar -xzf "$archive"
sudo mkdir -p /usr/local/bin
sudo install -m 755 portop /usr/local/bin/portop
portop
First launch: if macOS blocks portop
The binaries are not notarized by Apple. A browser download may trigger Gatekeeper. After checking that the binary comes from this repository's release, approve this app:
- Run
portop. If macOS shows “portop” Not Opened, click Done. - Open System Settings → Privacy & Security, find the message about portop, and click Open Anyway (or Allow Anyway, depending on macOS).
- Run
portopagain if prompted, confirm Open Anyway, and authenticate when macOS asks.
Adapted from the pkgtui first-launch guide. See Apple's instructions for your macOS version.
Uses the system's lsof and ps. Run sudo portop to include other users' processes; some sockets may otherwise be missing. Systemd, Docker Desktop VM process metadata and thread counts are unavailable on macOS.
x64 and ARM64. Pick one of the three options; each puts portop on your PATH.
# winget (Windows Package Manager)
winget install padovanl.portop
portop
Or get it from the Microsoft Store (search for portop): updates arrive automatically and portop works from any terminal.
Prefer the zip from the release?
Download portop_<version>_windows_amd64.zip (or windows_arm64) from the latest release, extract portop.exe and put it in a folder on your PATH. In PowerShell:
# replace <version> with the latest release number, no leading "v" $zip = "portop_<version>_windows_amd64.zip" # ARM64: windows_arm64 Invoke-WebRequest "https://github.com/padovanl/portop/releases/latest/download/$zip" -OutFile $zip $dir = "$env:LOCALAPPDATA\Programs\portop" Expand-Archive $zip -DestinationPath $dir -Force # add it to your user PATH, then open a new terminal and run portop [Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";$dir", "User")
Run it in Windows Terminal or any modern console. Socket owners are visible without elevation; open an Administrator terminal to see details of elevated or other users' processes.
curl -fLO https://github.com/padovanl/portop/releases/latest/download/portop_<version>_linux_amd64.tar.gz tar -xzf portop_<version>_linux_amd64.tar.gz sudo install -m 755 portop /usr/local/bin/portop
go install github.com/padovanl/portop/cmd/portop@latest
All architecture targets, Linux .deb/.rpm packages, Windows zips and checksums on the releases page.
Every key, one dashboard
The complete keybinding reference — also in-app any time with ?, and fully remappable via config.yml.
| Key | Action |
|---|---|
| ↑ / ↓ | Move the cursor |
| PgUp / PgDn | Move one visible page at a time |
| Mouse | Hover/select rows, wheel-scroll, open actions, and click headings to sort |
| g / G | Jump to top / bottom |
| enter | Process details — cmdline, exe, cwd, user, RSS, start time |
| k | Kill process, then y=SIGTERM or f=SIGKILL |
| o | Open http(s)://localhost:PORT in the browser |
| f / / | Filter/search by port, process or PID |
| v | Toggle IPv4 / IPv6 / both |
| e | Show/hide ESTABLISHED connections |
| s | Cycle sort column |
| c | Copy selected row to clipboard |
| n | Clear the new-port highlight |
| r | Refresh now |
| , | Settings — live theme picker, rebind any key |
| ? | Help |
| q | Quit |
