ps Command: Tutorial & Examples

One photograph of every process on your server — the tool you script against.

What It Is

ps is the snapshot tool. Where top is a live video — refreshing forever, made for staring at — ps takes a single still photograph of every process on the machine and prints it once. That tiny difference is the whole reason it exists: a snapshot you can grep, awk, wc -l, sort, pipe into a shell script, paste into a ticket. top is for looking with your eyes; ps is for talking to other programs.

It's installed on every Linux system, needs no setup, and like top it isn't doing anything magic — it just reads the kernel's live snapshot of the machine out of /proc and formats it. If you've never used it, the only thing that's actually hard about ps is the flags — and that mess has a historical explanation that, once you hear it, makes the whole tool snap into focus. We'll get there.

Your First Look

The single most useful invocation, and the one nine veterans out of ten reach for first:

ps aux

USER         PID %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
root           1  0.0  0.1 168400 11892 ?    Ss   May20   0:42 /sbin/init
root         412  0.0  0.0  35420  5380 ?    Ss   May20   0:01 /lib/systemd/systemd-journald
mysql       2323 12.3 37.7 19873204 6291456 ? Ssl May20 101:56 /usr/sbin/mysqld
www-data    2746  0.1  0.5 581632 89280 ?    S    May20   0:05 /usr/sbin/apache2 -k start
holger     18402  0.0  0.0  17220  4012 pts/0 R+  14:21   0:00 ps aux

One row per running process, every column you usually care about, sorted by PID (i.e. roughly by start order — line 1 is the init process, the ancestor of everything). Compared to top you lose the live refresh and the summary lines, but you gain the entire table at once — which means you can pipe it. That's the whole point.

The other invocation you'll see everywhere is ps -ef — the SysV-style spelling of roughly the same thing. Why two? Because of forty years of UNIX politics, which is the next section.

The Flag Zoo, Explained (the part nobody tells you)

Here's the genuinely surprising thing about ps, and it's the one piece of background that makes every tutorial you've ever read click: ps understands three different flag dialects at the same time, and they come from three different operating systems that hated each other in the 1980s.

  • BSD-style, no dash: ps aux, ps axjf. From Berkeley UNIX (the ancestor of macOS and the BSDs). Reads as: a stack of single letters with no leading -. a = all users, u = user-oriented format, x = include processes without a terminal, j = job format, f = forest (tree).
  • UNIX/SysV-style, single dash: ps -ef, ps -e -o pid,cmd. From AT&T System V (the ancestor of Solaris, AIX, HP-UX, and most Linux distributions). -e = every process, -f = full format, -o = pick your own columns.
  • GNU-style, double dash: ps --forest, ps --sort=-%mem. Linux additions that didn't exist on either side.

Linux ps was rewritten to accept all three at once, so every command-line you've ever seen for ps is correct — they're just different languages for asking similar things. ps aux and ps -ef produce nearly identical output. The man page is famously enormous because it has to document all three dialects in parallel; that's why it scares people. The shortcut: pick one dialect and stick to it. Most Linux veterans use aux for "everything" and -ef only when piping into other SysV-flavored tools. Once you know this, you stop trying to memorize random flag soup and start reading it.

(One small trap from this history: in BSD style, a leading - changes the meaning. ps ax and ps -ax are not quite the same command on every system — ps warns you about this. Stick to the dialect you started in.)

The Columns, Explained

ps aux columns, every one of them, in order:

  • USER — who owns the process. A surprise root where a service should be unprivileged is a security smell.
  • PID — the process ID. The number you feed to kill, renice, strace, /proc/<PID>/. Unique on the running system; reused after the process exits.
  • %CPU — share of one core, averaged over the lifetime of the process — so a service that ran hot for an hour then went idle for a day will show a small number here. This is the big difference from top, which shows an instantaneous %CPU. Surprises beginners.
  • %MEMRSS as a share of physical RAM. Sortable, and gold for hunting leaks.
  • VSZ — virtual size: total memory mapped (code + data + libraries + reserved). Often huge for Java or databases and not what's actually used. Don't panic over VSZ.
  • RSSresident set size: the actual RAM the process is using right now, in kilobytes. This is the memory number that matters. Same as RES in top.
  • TTY — the controlling terminal, or ? for daemons that have no terminal at all (which is most things on a server).
  • STAT — process state and a few flag letters glued together. The state is the first letter and tells a story: R running, S sleeping (the normal wait — most processes are here most of the time), D uninterruptible sleep (stuck in a disk or kernel call — you can't even kill a D process, see Gotchas), T stopped, Z zombie (finished but not yet reaped by its parent), I idle kernel thread. The trailing letters are flags: s session leader, l multi-threaded, + in the foreground process group of its terminal, < high priority, N low priority (niced).
  • START — when the process started. Recent processes show a clock time (14:21); older ones show the date (May20); really old ones show the year.
  • TIME — total CPU time consumed since launch (H:MM or MMM:SS), summed across all cores. A process showing days of TIME after a week of uptime has been very busy.
  • COMMAND — the command line with arguments, which is often the most diagnostic field of all (it tells you which python is running). Truncated at terminal width by default — use ps auxww to see the full line (each extra w widens it).

ps -ef instead gives you UID PID PPID C STIME TTY TIME CMD. The new column there is PPIDparent PID, the process that spawned this one. PPID is what lets you draw process trees (ps -ef --forest or pstree), and it's the column you reach for when you're chasing "who started this thing?"

Reading It by Example

The handful of ps recipes you'll use forever. These are the muscle memory.

The "what's eating my RAM" classic:

ps aux --sort=-%mem | head

Top ten memory hogs, biggest first. This is the single most common reason anyone runs ps on a server. (-%mem = descending by %MEM; drop the minus for ascending.) Same for CPU with --sort=-%cpu. Pipe into head to cap the rows, or into awk / cut to project the columns you want.

Find a process by name without remembering its PID:

ps aux | grep nginx | grep -v grep

The grep -v grep removes grep itself from the output — a tiny piece of UNIX folklore so universal it has its own pgrep shortcut:

pgrep -a nginx

(pgrep was invented precisely so you'd stop typing grep -v grep for the rest of your life. Use it.)

Pro Tip

if you only want the PIDs (to feed straight into kill or renice), drop the -a and use pgrep nginx | xargs kill — the whole "find process, kill process" loop becomes one line.

See the family tree of processes:

ps -ef --forest

or the BSD spelling:

ps axjf

You'll see init (or systemd) at the top, then sshd spawning a login shell spawning your shell spawning the ps you just typed. The first time you see this it makes Linux feel a lot less mysterious — every process has a parent, all the way up to PID 1.

One specific process, picked your way:

ps -o pid,user,%cpu,%mem,rss,command -p 2323

-o lets you pick exactly the columns you want, in the order you want — perfect for scripts and monitoring. (A short alias: -O adds your columns to the default set instead of replacing it.)

Every thread of a process (turns out a single PID can run on many cores at once):

ps -T -p 2323

Each row is one thread. A "single process at 800% CPU" in top is really one process with eight busy threads — this is the view that proves it.

A few whole-output patterns and what they mean:

  • One process with multi-day TIME and STAT R right now → a legitimately busy worker (or a runaway).
  • Many processes in STAT D piling up → an I/O subsystem is stuck (a disk hang, a frozen NFS mount). The classic "load average 100, CPU idle" story — see top's Reading-by-Example for the full anecdote.
  • A pile of STAT Z zombies sharing a PPID → the parent has a bug: it's spawning children and never calling wait() to reap them. The fix is in the parent, not the zombies.
  • RSS for a single process climbing every snapshot, hour after hour → a memory leak. ps is how people prove leaks before reaching for a profiler.

Cheat Sheet

The commands worth memorizing:

  • ps aux — everything, BSD style. Default starting point.
  • ps -ef — everything, SysV style. Same idea, different dialect.
  • ps aux --sort=-%mem | head — top memory consumers.
  • ps aux --sort=-%cpu | head — top CPU consumers (averaged, not live — use top for live).
  • ps -ef --forest — process tree, with parent/child indented.
  • ps -p PID -o pid,user,%cpu,%mem,rss,command — one process, exactly the columns you want.
  • ps -T -p PID — threads of one process.
  • ps auxww — full command line, no truncation (one w widens, two w's unlimited).
  • pgrep -a name — find PIDs by name without grep -v grep.

ps aux columns at a glance:

Column Meaning
USER Owner of the process
PID Process ID
%CPU CPU share of one core, averaged over the process's lifetime
%MEM RSS as a share of physical RAM
VSZ Virtual size — total memory mapped, in KiB (not what's used)
RSS Resident set size — actual RAM in use right now, in KiB
TTY Controlling terminal, or ? for none (daemons)
STAT Process state plus flag letters
START When the process started (clock time, date, or year)
TIME Total CPU time consumed since launch
COMMAND Command line with arguments (truncated; use ps auxww)

STAT codes:

Code Meaning
R Running or runnable
S Interruptible sleep — the normal wait
D Uninterruptible sleep — usually disk/NFS wait; can't be killed
Z Zombie — dead, not yet reaped by its parent
T Stopped
I Idle kernel thread
< High priority
N Low priority (niced)
s Session leader
l Multi-threaded
+ In the foreground process group

How You'll Actually Use It

In practice, ps lives in three places: a --sort=-%mem | head you type when something feels off; a one-liner inside a shell script that needs to know whether a daemon is running; and a copy-paste into a chat or ticket when you need to show someone what's running on the box. If you find yourself wanting to watch a process change, you've outgrown ps for that moment — that's top or htop. If you want to find a process by name, that's pgrep. If you want to kill one, that's kill or pkill. ps is the tool you reach for when the next step is a pipe, not a keystroke.

Gotchas

  • %CPU is the average over the process's lifetime, not now. A web server that's been up for a month and was hammered for one hour will read tiny here, even if it's currently on fire. Cross-check with top (live) or read /proc/<PID>/stat twice and diff.
  • VSZ is not real memory — read RSS. Same trap as VIRT vs RES in top.
  • COMMAND is truncated. Two services that look identical in ps aux may be running completely different things — always use ps auxww (or -o command last) when the command line matters.
  • A D-state process won't die. kill -9 doesn't reach it until its I/O returns. If you see them pile up, the problem isn't the processes, it's whatever they're stuck waiting on.
  • PID is reused. When a process exits, its PID can be handed to a brand-new, unrelated process. Don't trust a PID you saved an hour ago without verifying.
  • ps itself appears in its own output. Cute but occasionally surprising in scripts — filter it out with grep -v ps or skip the issue entirely with pgrep.

History & Philosophy

ps is one of the oldest commands in UNIX — it shipped in the very first edition in 1971, before there was a kernel/userspace split worth speaking of. Its job hasn't changed in fifty years: take a snapshot of the process table and print it. What's changed is the table, which now lives in the /proc virtual filesystem and is published by the kernel as a directory per PID:

ls /proc/2323/

cmdline  comm  cwd  environ  exe  fd/  io  limits  maps  mem  mounts
root  stat  statm  status  task/  ...

Everything ps prints is just files in there. cat /proc/2323/status gives you a human-readable version of what ps -p 2323 formats into columns; /proc/2323/cmdline is the COMMAND column. ps doesn't ask the kernel; it reads files. The same insight as top, and the same payoff: once you see that a running Linux box is just a tree of files describing itself, you stop being afraid of it and start poking around. That's the "everything is a file" idea, and ps is one of the cleanest examples of it.

The three-dialect flag mess is the other part of the history — a fossil of the UNIX wars, preserved in amber, that you now know how to read.

See Also

  • top — the live version; use it when you want to watch
  • htop — the friendlier interactive cousin of top
  • pgrep — find PIDs by name without grep -v grep
  • pkillkill by name in one step
  • kill / renice — act on a PID once you've found it
  • pstree — the process tree on its own, more readable than --forest
  • /proc — where all of this actually lives
  • process — what a process is, threads, parents, zombies
  • memory leak — the classic case for watching RSS over time

Tired of typing ps aux --sort=-%mem | head at 3am?

CleverUptime watches every process on every server, every minute, and tells you in plain language which one is leaking, which one is pinned, and which one to look at first — no flag dialects required.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →