ps [options]
ps aux
ps takes a SNAPSHOT of the processes running on the system: who started them, how much CPU and memory they use, how long they have been running, and the exact command line. Use it to find
what is running, hunt runaway or stuck processes, find the parent of a process, check who owns what, and feed process lists into scripts. Unlike top and htop (live views), ps prints once and
exits, which makes it perfect for scripts and pipelines. This sheet covers the everyday commands, choosing columns, sorting, filtering, threads and trees, process states, and one-liners.
It is written for Linux (procps-ng). macOS and BSD ps differ in places (noted at the end).
Three styles of options (this confuses everyone)
ps has grown three option styles that all work on Linux:
UNIX style Options start with a dash, and can be grouped: ps -ef ps -eF ps -u alice
BSD style NO dash: ps aux ps axjf ps ax
GNU long Two dashes and a word: ps --forest ps --sort=-%mem ps --no-headers
Mixing them can change the meaning: ps -aux (with a dash) means "user x"... which prints a warning on Linux. Type ps aux for BSD style and ps -ef for UNIX style.
Most people memorize two commands: ps aux (BSD style, all processes with CPU and memory) and ps -ef (UNIX style, all processes with parent PIDs).
The commands you will use most
ps # only YOUR processes in THIS terminal (a very short list: your shell and ps itself)
ps aux # every process on the system, with the user, CPU and memory: the classic
ps -ef # every process with the parent PID (PPID) and start time: the classic UNIX view
ps -eF # like -ef but with extra columns (size, RSS, the CPU it ran on)
ps -ef --forest # a tree: children drawn under their parents ps axjf the BSD style tree pstree -p a nicer tree tool
ps -u alice # processes of user alice (effective user) ps -U alice real user ps -u root
ps -p 1234 # one process by PID ps -p 1234,5678 several ps -p $$ my current shell
ps -C nginx # processes whose COMMAND NAME is exactly nginx ps -C nginx,php-fpm
ps aux | grep '[n]ginx' # search the full list. The [n] trick stops grep from matching itself
ps -eo pid,ppid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head # a custom table sorted by CPU (see the next sections)
ps -o pid,cmd --ppid 1 # children of a given parent (here: PID 1, systemd)
ps -t pts/0 # processes attached to a terminal ps -t '?' processes with no terminal (daemons: quote the ?)
ps -e --no-headers | wc -l # how many processes are running
Tip: ps aux | less -S keeps long lines from wrapping. Use ps auxww so the command line is NOT cut at the terminal width.
Reading the output of ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 168584 12960 ? Ss Sep25 0:07 /sbin/init
alice 2311 1.2 2.4 2846712 391604 ? Sl 09:14 1:05 /usr/lib/firefox/firefox
USER Who owns (started) the process
PID The process ID: a unique number. Use it with kill, renice, strace, lsof -p, and /proc/PID
%CPU CPU time used divided by the time the process has been running (a LIFETIME AVERAGE, not the current usage). A process that was busy earlier can still show a high number. For live CPU use top or htop
%MEM The share of physical memory (RAM) the process occupies (resident set size)
VSZ Virtual size in KB: all the address space the process has mapped. It is often huge and not a good measure of memory use
RSS Resident Set Size in KB: RAM the process is using now. This is the number to look at for memory. Shared libraries are counted in every process that uses them, so RSS totals can exceed real RAM
TTY The terminal the process is attached to (pts/0, tty1). A ? means no terminal (a daemon or service)
STAT The state of the process (see below)
START When it started (a time for today, or a date)
TIME Total CPU time consumed, in minutes:seconds. Not the same as how long it has been running (that is ELAPSED / etime)
COMMAND The command and its arguments (truncated to the screen width unless you use ww). Names in [brackets] are kernel threads, like [kworker/0:1]
In ps -ef the columns are: UID PID PPID C STIME TTY TIME CMD. PPID is the parent's PID. C is a recent CPU usage figure.
Process states (the STAT column)
Main letter:
R Running or runnable (using CPU or waiting for a turn)
S Sleeping, waiting for something (interruptible). Most processes are in this state most of the time. Normal
D Uninterruptible sleep, usually waiting on disk or network I/O (NFS, a failing disk). It cannot be killed until the I/O finishes. Many D processes = an I/O problem
T Stopped (Ctrl+Z, or a debugger, or SIGSTOP). Resume with kill -CONT PID or fg
t Stopped by a debugger during tracing
Z Zombie: finished, but its parent has not collected its exit status yet. It uses no CPU or memory, only a process table entry. Fix the parent (or restart it)
I Idle kernel thread (newer kernels) X Dead (you should never see this)
Extra characters after the letter:
s Session leader < High priority (nice value below 0) N Low priority (nice above 0) L Has pages locked in memory
l Multi-threaded + In the foreground of its terminal W Swapped out (old kernels)
Example: Ssl = sleeping, session leader, multi-threaded. R+ = running in the foreground. D = stuck in I/O. Z or Z+ = zombie.
Find problem states:
ps -eo stat,pid,ppid,user,cmd | awk '$1 ~ /^D/' # processes stuck in uninterruptible sleep (I/O problems)
ps -eo stat,pid,ppid,user,cmd | awk '$1 ~ /^Z/' # zombies (then look at their PPID: the parent is the one to fix)
ps -eo stat,pid,ppid,cmd | awk '$1 ~ /^T/' # stopped jobs
Choose your own columns: -o
ps -eo pid,ppid,user,%cpu,%mem,rss,etime,cmd # a useful everyday view
ps -o pid=,cmd= -p 1234 # a trailing = removes the header for that column (script friendly) ps -o rss= -p 1234 just the memory number
ps -eo pid:8,user:12,comm:20,args # set column widths with :N
Common column names (-o):
pid Process ID ppid Parent process ID pgid / sid Process group / session ID
user Effective user name (uid for the number) euser ruser Effective / real user group Group name
%cpu pcpu CPU% over the process lifetime %mem pmem Memory % rss Resident memory (KB) vsz Virtual memory (KB)
etime Elapsed time since it started ([[dd-]hh:]mm:ss) etimes the same in seconds lstart Full start date and time start_time start time (short)
time Cumulative CPU time stat state The state nice ni Nice value (-20 high priority to 19 low) pri priority
comm Command name only (no arguments, max 15 characters) cmd args The full command line command the full command as typed exe path of the executable
tty Terminal nlwp thcount Number of threads psr The CPU core it last ran on wchan The kernel function it is waiting in (why it sleeps)
tid Thread ID (with -L) uid gid Numeric IDs lwp Thread ID (same as tid) sgi_p CPU running on now (R state)
cgroup Control groups (containers, systemd services) unit The systemd unit of the process slice the systemd slice ouid the login user's UID
List every available column: ps L
Numeric and shell-friendly output (no name lookups): ps -eo uid,pid,cmd
Wide command lines: ps -eo pid,args -ww (-w wide, -ww unlimited) Or set the width: COLUMNS=250 ps aux
Sorting and top-N lists
ps aux --sort=-%cpu | head -10 # top CPU users (- means descending) remember %cpu is a lifetime average
ps aux --sort=-%mem | head -10 # top memory users
ps aux --sort=-rss | head -10 # sort by real memory (RSS)
ps -eo pid,user,%mem,rss,cmd --sort=-rss | head -15 # a neat top-15 memory table
ps -eo pid,etime,cmd --sort=-etime | head # the longest-running processes (oldest first) --sort=etime the newest
ps -eo pid,lstart,cmd --sort=lstart | tail -5 # the most recently started processes
ps -eo pid,nlwp,cmd --sort=-nlwp | head # the processes with the most threads
ps -eo pid,%cpu,%mem,cmd --sort=-%cpu,-%mem | head # sort by several keys: CPU first, then memory
Sort keys work with any column name: --sort=pid --sort=user --sort=-vsz --sort=start_time.
For a live sorted view use top -o %CPU, htop, or watch -n 2 'ps aux --sort=-%cpu | head'.
Filter and select
ps -u alice -o pid,%cpu,%mem,cmd # one user's processes ps -U alice -u alice real and effective
ps -C sshd -o pid,ppid,user,cmd # by exact command name
ps -p $(pgrep -d, nginx) -o pid,ppid,%cpu,%mem,cmd # feed pgrep's PIDs to ps (-d, joins them with commas)
ps --ppid 1234 -o pid,cmd # direct children of PID 1234
ps -g 1234 # a process group ps -s 1234 a session ps --sid 1234
ps -eo pid,user,cmd | awk '$2=="www-data"' # filter columns with awk
ps -eo user | sort | uniq -c | sort -rn # how many processes each user has
ps -eo comm | sort | uniq -c | sort -rn | head # which programs run the most instances
ps -e -o pid,comm | grep -E 'nginx|apache|httpd' # web servers
ps -N -u root -o pid,user,cmd # everything EXCEPT root's processes (-N negates)
pgrep and pkill are often better for name searches (below).
Threads
ps -eLf # every THREAD (LWP column) with a line each ps -L -p 1234 the threads of one process
ps -eLo pid,tid,psr,%cpu,stat,comm | head # thread ID, and which CPU core it ran on
ps -o pid,nlwp,cmd -p 1234 # the thread count of one process (nlwp)
ps -eLf | wc -l # total threads on the system cat /proc/sys/kernel/threads-max the limit
ps -T -p 1234 # the threads of a process (SPID column) ps H -p 1234 the BSD style
A process with thousands of threads or a steadily growing thread count often signals a leak. Compare with ulimit -u and the systemd TasksMax setting.
Trees and parents (who started what)
ps -ef --forest # a tree view with ASCII lines ps auxf BSD style tree ps -eo pid,ppid,cmd --forest
ps -ejH # process tree with session and group IDs (indented)
pstree -p # a nicer tree (sudo apt install psmisc) pstree -ap 1234 a subtree with arguments pstree -u alice one user's processes
ps -o ppid= -p 1234 # the PARENT PID of a process ps -o pid,ppid,cmd -p $(ps -o ppid= -p 1234) who is the parent?
Following the parent chain finds the real culprit: an orphan (PPID 1) often means a daemon or a process whose parent exited. Zombies are cleared by fixing their PARENT, not by killing the zombie.
systemd view: systemctl status PID or systemd-cgls shows which service a process belongs to ps -o pid,unit,slice,cmd -p PID
Time: how long has it been running?
ps -o pid,etime,cmd -p 1234 # elapsed time since it started: 03-04:21:07 = 3 days 4 hours 21 minutes 7 seconds
ps -o pid,etimes= -p 1234 # elapsed seconds (great for scripts)
ps -o pid,lstart -p 1234 # the full start date and time ps -o pid,start_time -p 1234
ps -eo pid,etimes,cmd | awk '$2 > 86400 {print}' # everything running more than a day
ps -eo pid,etime,cmd --sort=etime | tail # the oldest processes
ps -o pid,time,cmd -p 1234 # total CPU TIME used (not elapsed): 12:34 means 12 minutes of CPU
Started long ago and still using a lot of CPU TIME: a candidate for a leak or a busy loop. Started long ago, TIME small: an idle, healthy service.
Memory and CPU: what is really being used
ps -eo pid,user,rss,vsz,%mem,cmd --sort=-rss | head # RSS (real RAM) versus VSZ (address space)
ps -o rss= -p 1234 | awk '{printf "%.1f MB\n", $1/1024}' # one process, in MB
ps -eo rss= | awk '{s+=$1} END {printf "%.1f MB total RSS\n", s/1024}' # a rough total (double counts shared memory)
ps -eo user=,rss= | awk '{a[$1]+=$2} END {for (u in a) printf "%-12s %8.1f MB\n", u, a[u]/1024}' | sort -k2 -rn # memory per user
ps -eo comm=,rss= | awk '{a[$1]+=$2} END {for (c in a) printf "%-20s %8.1f MB\n", c, a[c]/1024}' | sort -k2 -rn | head # memory per program (all its processes together)
Better memory numbers than RSS (shared pages counted once): smem -tk or grep -E 'Pss|Rss' /proc/PID/smaps_rollup and system totals with free -h
CPU: %cpu in ps is an AVERAGE since the process started. For what is happening NOW use top -b -n1 | head -20 , pidstat 1 5 -p PID (sysstat), or htop.
Out-of-memory kills: dmesg -T | grep -i 'killed process' journalctl -k | grep -i oom ps shows the survivors: the victim is the process with the highest oom_score: cat /proc/PID/oom_score
Priority and scheduling
ps -eo pid,ni,pri,cls,rtprio,cmd | head # nice value (ni), priority, and scheduling class (TS = normal, FF/RR = real time)
ps -eo pid,ni,cmd --sort=ni | head # the lowest nice value (highest priority) first
renice -n 10 -p 1234 # lower the priority of a running process sudo renice -n -5 -p 1234 raise it nice -n 10 command start a command with lower priority
ionice -p 1234 ; ionice -c3 -p 1234 # I/O scheduling class (idle = 3) for disk-heavy processes
taskset -cp 1234 # which CPU cores it may use ps -o pid,psr,cmd -p 1234 the core it last ran on
A nice value of 19 is the lowest priority, -20 the highest. Only root can lower the value (raise the priority).
Environment, files and the /proc filesystem
ps eww -p 1234 # show the process's ENVIRONMENT variables after the command (e = environment, ww = do not truncate) may show secrets: be careful
sudo cat /proc/1234/environ | tr '\0' '\n' # the same from /proc (root or the owner)
cat /proc/1234/cmdline | tr '\0' ' '; echo # the exact command line cat /proc/1234/status state, memory, threads, capabilities, UIDs readlink /proc/1234/exe the executable
ls -l /proc/1234/fd | head # the files and sockets it has open ls -l /proc/1234/cwd its working directory cat /proc/1234/limits resource limits cat /proc/1234/cgroup
sudo lsof -p 1234 # the same and more: files, sockets, libraries (see the lsof sheet) sudo ss -tunp | grep 1234 network sockets of a PID
sudo strace -p 1234 -f -tt -e trace=network # what is it doing right now? (see the strace sheet) cat /proc/1234/wchan; echo the kernel function it sleeps in
ps -o pid,wchan:20,cmd -p 1234 # why a D-state process is stuck (for example nfs_... or io_schedule)
Command lines are visible to every user (ps aux). NEVER pass passwords or tokens as command-line arguments: any user on the machine can read them, and they end up in logs. Use files, environment variables, or prompts instead.
On hardened systems: mount -o remount,hidepid=2 /proc hides other users' processes from non-root users.
pgrep, pkill and pidof: find and signal by name
pgrep nginx # PIDs of processes whose NAME contains "nginx" pgrep -x sshd exact name pgrep -l nginx with names pgrep -a nginx with the full command line
pgrep -f "python3 app.py" # match against the FULL command line (needed for scripts run by an interpreter)
pgrep -u alice # a user's processes pgrep -u root sshd combine pgrep -c nginx just count them
pgrep -n firefox pgrep -o firefox # the newest / oldest match
pgrep -P 1234 # children of PID 1234 pgrep -t pts/1 on a terminal
pkill nginx # send SIGTERM (ask nicely) to every match pkill -f "app.py" match the full command line pkill -u alice all of a user's processes
pkill -9 -f runaway.sh # SIGKILL: last resort (no clean-up) pkill -HUP nginx reload (many daemons re-read config on HUP)
pidof nginx # PIDs of a program by name (simple) pidof -s nginx just one PID
kill 1234 ; kill -15 1234 ; kill -TERM 1234 # ask the process to stop (SIGTERM, the default) kill -9 1234 force (SIGKILL) kill -HUP 1234 reload kill -0 1234 && echo alive is it still there?
kill -STOP 1234 ; kill -CONT 1234 # pause and resume a process
killall firefox # by name (on Linux: all processes with that exact name; careful: on Solaris killall means kill EVERYTHING)
Signals: 15 TERM (polite, the default) 9 KILL (cannot be caught) 1 HUP (reload/hangup) 2 INT (Ctrl+C) 3 QUIT 19/18 STOP/CONT 10/12 USR1/USR2 (app-defined) List them: kill -l
Try TERM first and wait a few seconds. KILL leaves temporary files, locks and shared memory behind. A process in D state ignores signals until its I/O returns.
Always check what you match BEFORE you kill: pgrep -a pattern shows exactly what pkill pattern would hit. A pattern like pkill -f java can hit unrelated programs.
Containers, services and cgroups
ps -eo pid,cgroup:60,cmd | head # which cgroup (systemd unit or container) each process is in
ps -eo pid,user,cmd,cgroup | grep -E 'docker|containerd|kubepods' # find container processes from the HOST
ps -ef | grep -v grep | grep containerd-shim # Docker/containerd shim processes (one per container)
docker top mycontainer # ps-style list of a container's processes docker exec mycontainer ps aux (only if ps exists in the image; many minimal images lack it: use /proc)
systemctl status nginx # a service's processes in a tree, with the main PID systemd-cgls the whole cgroup tree systemd-cgtop live usage by service
A process seen from the host has a different PID than inside its container (PID namespaces). Use docker inspect -f '{{.State.Pid}}' NAME to get the host PID, then ps -p PID .
Minimal containers (Alpine/BusyBox) have a reduced ps that lacks many options: check what it offers with ps --help or read /proc directly.
Practical one-liners
# What is eating my CPU and memory right now?
ps -eo pid,user,%cpu,%mem,rss,etime,cmd --sort=-%cpu | head -10 ; echo ; ps -eo pid,user,%cpu,%mem,rss,etime,cmd --sort=-%mem | head -10
# Everything belonging to a service, in a tree
ps -eo pid,ppid,user,%cpu,%mem,etime,cmd --forest | grep -A5 '[n]ginx'
# Kill a process you found by name, safely (show first, then kill)
pgrep -a myapp ; read -r -p "Kill these? [y/N] " a && [ "$a" = y ] && pkill myapp
# Find zombies and their parents
ps -eo stat,pid,ppid,user,cmd | awk '$1 ~ /^Z/ {print; system("ps -o pid,cmd -p " $3)}'
# Find processes in uninterruptible sleep and why
ps -eo stat,pid,wchan:22,cmd | awk '$1 ~ /^D/'
# Who is running what? (count per user)
ps -eo user= | sort | uniq -c | sort -rn
# Processes running for more than 7 days
ps -eo pid,etimes,user,cmd | awk '$2 > 604800 {printf "%s %.1f days %s %s\n", $1, $2/86400, $3, substr($0, index($0,$4))}'
# The 5 processes with the most threads
ps -eLo pid,comm | sort | uniq -c | sort -rn | head -5
# Which process is using a port? (ps cannot tell you: use ss)
sudo ss -tulpn | grep ':8080' then ps -o pid,user,etime,cmd -p PID
# Watch the top CPU processes every 2 seconds
watch -n 2 'ps -eo pid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head -15'
# Show a process and its whole family (parent and all children)
pstree -ap $(pgrep -o nginx) or ps -o pid,ppid,cmd -p PID --ppid PID
# Get a process's full command line without truncation, on one line
ps -o args= -p 1234 -ww
# Count how many processes match a name
pgrep -c -x sshd
# Was a program started by cron or a person? Look at the parent
ps -o pid,ppid,user,cmd -p PID ; ps -o pid,cmd -p $(ps -o ppid= -p PID)
# Log a snapshot of the busiest processes every minute (cron)
* * * * * ps -eo pid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head -6 >> /var/log/top-cpu.log
# Show only processes older than a start time (kill idle sessions)
ps -eo pid,tty,etimes,user,cmd | awk '$2 ~ /pts/ && $3 > 28800'
# Simple watchdog: is my service running?
pgrep -x myservice >/dev/null || echo "myservice is DOWN"
macOS and BSD differences
ps -ax # every process on macOS/BSD (no dash: ps aux also works) ps -axo pid,ppid,%cpu,%mem,etime,command columns work, but the names differ slightly (command instead of cmd, no --sort or --forest)
ps -axo pid,%cpu,%mem,comm | sort -k2 -nr | head # sort by piping to sort instead of --sort
ps -axo lstart,pid,command # start times pgrep -lf pattern also works on macOS
The GNU long options (--sort, --forest, --no-headers, --ppid) do NOT exist on macOS/BSD. Install GNU tools with brew install procps for the same behavior (the command is then ps from procps-ng).
BusyBox (Alpine) ps supports only a small set of options (-o pid,user,comm,args, and no --forest). Use /proc for more.
ps, top, htop and friends
ps A one-off SNAPSHOT you can filter and script. Best for scripts, logs, pipelines, and asking exact questions
top Live, interactive view of CPU and memory (press P for CPU sort, M for memory, 1 for each core, k to kill)
htop A friendlier live view with trees, mouse, search and kill (see the htop sheet)
pgrep/pkill Find and signal processes by name and attributes
pstree A clear picture of parent and child relationships
pidstat Per-process CPU, memory and I/O over time (sysstat package: pidstat -u -r -d 1)
lsof / ss Which files and network ports a process has open (see the lsof sheet)
strace / perf What a process is doing right now (see the strace and perf sheets)
systemctl For services: systemctl status NAME shows the main PID and its children
Common problems
"ps: warning: bad syntax, perhaps a bogus '-'" You mixed styles (for example ps -aux). Use ps aux (no dash) or ps -ef
ps shows only a few processes You ran plain ps (only your terminal). Add -e or aux
The COMMAND column is cut off Add ww (ps auxww) or use -o args with COLUMNS set. When piping, ps already stops truncating
grep shows itself in the results Use the bracket trick grep '[n]ginx' , or use pgrep instead
%CPU shows more than 100% The process is multi-threaded and uses several cores (100% = one core). It is also a lifetime average
%CPU is high for a process that is idle now %CPU is averaged over the process's whole life. Use top or pidstat to see the CURRENT usage
VSZ is huge but the machine has plenty of free memory VSZ counts reserved address space, not used RAM (Java and browsers reserve a lot). Look at RSS
A zombie (Z) process will not go away You cannot kill a zombie: it is already dead. Restart or fix its PARENT (ps -o ppid= -p PID), and the parent will reap it, or PID 1 adopts it
kill does nothing The process ignores TERM (send KILL, as a last resort), is in D state (waiting on I/O: fix the disk or NFS), or you lack permission (use sudo)
D-state processes pile up A stuck disk, an NFS server that is down, or a hung mount. Check dmesg, mount, and the wchan column; fix the storage
"ps: command not found" in a container The image has no procps. Install it (apt install procps) or read /proc: for p in /proc/[0-9]*; do echo "$p $(tr '\0' ' ' < $p/cmdline)"; done
The PID I see differs from the container's PID PID namespaces. Find the host PID with docker inspect, or use docker top
My process does not appear It exited already, you are in another PID namespace (container), or hidepid hides it. Use sudo and check journalctl
Very long lines break my awk parsing Use -o with fixed columns and put cmd or args LAST so spaces in the command do not shift the fields
Different columns on different systems procps versions and BSD differ. Specify columns explicitly with -o so output is predictable in scripts
Times look wrong START is in local time. TIME is CPU time, not elapsed. Use etime/etimes for elapsed time
Tips
Remember two commands: ps aux (or ps auxww) for everything with CPU and memory, and ps -ef --forest for the parent and child tree.
Use -o to choose exactly the columns you need (pid,ppid,user,%cpu,%mem,rss,etime,cmd), and put cmd last so awk parsing stays reliable.
Read %CPU as a lifetime average. For what is happening now use top, htop or pidstat. Use RSS, not VSZ, for real memory use.
Learn the STAT letters: R running, S sleeping, D stuck in I/O, T stopped, Z zombie. Fix zombies through the parent and D-state through the storage or network.
Use pgrep -a pattern to see what a pkill would hit before you run it, and try SIGTERM before SIGKILL.
Use etime and lstart to see how long something has really been running, and ppid with --forest or pstree to see who started it.
Never put passwords or tokens on a command line: every user can read command lines in ps and /proc.
In containers, use docker top or the host's ps with the cgroup column. Minimal images may not have ps at all: read /proc instead.
Use watch -n 2 with ps for a quick live view, or log periodic snapshots with cron when you are chasing an intermittent problem.
On macOS and BSD the GNU long options do not exist: pipe to sort or install GNU procps.