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.
{}systemd & Docker aware
Every row shows the owning .service unit and Docker container, resolved from cgroups — 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. 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. |
Archive names: portop_<version>_<target>.tar.gz. 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, 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. - Validation: build/emulation checks do not replace physical ARM or native Mac testing. See the validation report for the checks performed.
Detects all six 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.
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 six architecture targets, Linux .deb/.rpm packages 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 |
