A cross-platform library for retrieving information about running processes, network connections, and system resource utilization (CPU, memory, disk, network).
psutil (process and system utilities) exposes a uniform API over OS internals on Linux, Windows, and macOS: enumerate processes, inspect open connections and listening ports, read CPU/RAM/disk usage, and list logged-in users. It is the Python equivalent of ps, netstat, top, and lsof combined. In security work it powers host inventory, live-response triage, and lightweight monitoring or detection scripts.
pip install psutilimport psutil
print("cpu %:", psutil.cpu_percent(interval=1))
print("memory %:", psutil.virtual_memory().percent)
for conn in psutil.net_connections(kind="inet"):
if conn.status == psutil.CONN_LISTEN:
print(conn.laddr.ip, conn.laddr.port, conn.pid)Listing sockets owned by other users requires root; without it those entries appear with pid=None.
| API | Purpose |
|---|---|
psutil.process_iter(attrs=[...]) |
Iterate processes efficiently |
psutil.Process(pid) |
A single process |
proc.name(), .exe(), .cmdline(), .username() |
Process identity |
proc.ppid(), .create_time(), .status() |
Lineage and state |
proc.open_files(), .connections() |
Resources held |
psutil.net_connections(kind="inet") |
System-wide sockets |
psutil.net_if_addrs() / net_if_stats() |
Interfaces and link state |
psutil.cpu_percent(), virtual_memory(), disk_usage(path) |
Resource metrics |
psutil.users(), boot_time() |
Session and uptime data |
psutil.AccessDenied, NoSuchProcess, ZombieProcess |
Exceptions you must handle |
Process objects are live: a process can vanish between two attribute reads.
Enumerate processes with their owner and command:
import psutil
for proc in psutil.process_iter(["pid", "name", "username"]):
info = proc.info
print(f"{info['pid']:>6} {info['username'] or '-':<12} {info['name']}") 1 root systemd
842 root sshd
1337 tester python3
List listening ports and the processes behind them (host triage):
import psutil
for conn in psutil.net_connections(kind="inet"):
if conn.status == psutil.CONN_LISTEN:
laddr = f"{conn.laddr.ip}:{conn.laddr.port}"
name = psutil.Process(conn.pid).name() if conn.pid else "?"
print(f"LISTEN {laddr:<22} pid={conn.pid} ({name})")LISTEN 0.0.0.0:22 pid=842 (sshd)
LISTEN 127.0.0.1:5432 pid=990 (postgres)
LISTEN 0.0.0.0:8080 pid=1337 (python3)
Snapshot system resource usage:
import psutil
print("CPU %:", psutil.cpu_percent(interval=1))
mem = psutil.virtual_memory()
print(f"RAM : {mem.percent}% used ({mem.used // 1024**2} MB / {mem.total // 1024**2} MB)")
disk = psutil.disk_usage("/")
print(f"Disk : {disk.percent}% used")
print("Boot :", psutil.boot_time())CPU %: 12.5
RAM : 43.2% used (3450 MB / 7987 MB)
Disk : 61.0% used
Boot : 1721160000.0
- Host & process inventory — enumerate running processes, owners, and command lines for asset and baseline collection.
- Live-response triage — during incident review of a system you administer, list open connections and listening ports to spot suspicious services.
- Malware / anomaly hunting — flag processes with odd names, unexpected network listeners, or unusual resource spikes.
- Detection scripting — build lightweight monitors that alert on new listeners, high CPU, or unexpected users.
- Persistence checks — inspect startup processes and connections when validating a host's integrity.
- Not catching
NoSuchProcess— the process table changes while you iterate it. - Not catching
AccessDenied— without root, many attributes are unreadable and raise. - Calling
cpu_percent()without an interval — the first call always returns0.0; passinterval=1or call it twice. - Using
process_iter()withoutattrs=, which is far slower because each attribute is fetched separately. - Assuming full visibility in a container — namespaces limit what is visible, by design.
- Treating
proc.name()as trustworthy — a process name is attacker-controllable; corroborate withexe()andcmdline(). - Expecting identical fields across platforms — availability differs between Linux, macOS, and Windows.
[!warning] Authorized use only Inspect only systems you own or are authorized to administer or monitor.
- This is a local introspection tool, not a network scanner. It reads the host's own state and generates no traffic — which makes it safe to run, and useless for remote discovery.
- The output is highly sensitive. A process and socket inventory reveals every running service, its command line (which may contain credentials passed as arguments), and the local topology.
- Command lines leak secrets.
proc.cmdline()frequently exposes passwords and tokens that other tools were careless enough to accept as arguments — a good demonstration of why your own tools must not. - Running as root reveals other users' processes. Do that only where you are authorized to see them.
- Process names are attacker-controlled. Malware routinely masquerades as a legitimate process name; verify the executable path and hashes.
- Sockets bound to
0.0.0.0are the finding worth acting on — services exposed beyond loopback, often unintentionally.
- Wrap per-process access in
try/except (psutil.NoSuchProcess, psutil.AccessDenied)— processes vanish and permissions vary. - Run with sufficient privileges to see all processes/connections (root/admin for full visibility).
- Prefer
process_iter(attrs=[...])to batch-fetch fields efficiently rather than querying attributes one by one. - Call
cpu_percent(interval=...)with an interval, or the first reading returns0.0. - Cache
pid/create_timetogether to avoid PID-reuse confusion in long-running monitors.
- [[Paramiko]] — gather psutil inventory across hosts over SSH
- [[Rich]] — render process/connection tables cleanly
- [[Readme|Python for Security Professionals]] — course home