When a machine is slow, unresponsive, or something is running that should not be, these are the tools that tell you what is actually happening. ps takes a snapshot, top and htop watch continuously.

ps: a snapshot

ps has two incompatible sets of options for historical reasons — BSD style without dashes and UNIX style with them. Two invocations cover almost everything:

ps aux        # BSD style — everything, with CPU and memory
ps -ef        # UNIX style — everything, with parent PIDs

Use aux when you care about resource usage, -ef when you care about which process started which.

$ ps aux | head -3
USER  PID %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
root    1  0.0  0.1 168404 11928 ?    Ss   Aug21   0:14 /sbin/init
www     2  1.3  2.4 984320 98204 ?    Sl   09:12   4:21 /usr/sbin/nginx
ColumnMeaning
PIDProcess ID — what you pass to kill
%CPUCPU used, averaged over the process’s whole lifetime
%MEMShare of physical RAM
VSZVirtual memory reserved — usually meaningless, see below
RSSResident set size: physical RAM actually in use, in KB
STATProcess state — see the table below
TIMETotal CPU time consumed, not elapsed time

Watch out for %CPU here. It is a lifetime average, so a process that pinned a core for an hour yesterday and has been idle since will still show a high number. For what is happening now, use top.

Process states

CodeState
RRunning or runnable
SSleeping, waiting for something — the normal state for most processes
DUninterruptible sleep, almost always disk or network I/O
ZZombie — finished, but its parent has not collected the exit status
TStopped, usually by Ctrl+Z or a debugger

Suffixes add detail: s is a session leader, l is multi-threaded, + is in the foreground, < is high priority, N is low priority.

D is the one that matters when things hang. A process in uninterruptible sleep cannot be killed, not even with -9, because it is inside a kernel call waiting on hardware. Several processes stuck in D usually means a failing disk or a dead network mount, not a software bug.

Zombies are almost never the problem. A zombie holds a process table entry and nothing else — no memory, no CPU. You cannot kill one; it is already dead. Fix the parent, or the zombies disappear when the parent exits and init reaps them.

Picking columns and sorting

# Top 10 memory consumers
ps -eo pid,user,rss,comm --sort=-rss | head -11

# Top 10 CPU consumers
ps -eo pid,user,pcpu,comm --sort=-pcpu | head -11

# With the full command line and start time
ps -eo pid,ppid,lstart,etime,cmd --sort=start_time

# Just one process, by name
ps -C nginx -o pid,rss,etime

# Process tree
ps -ejH
pstree -p

-o lets you ask for exactly the columns you want, and --sort=-field ranks by them descending. This is far better than piping ps aux through sort and guessing at column numbers.

Finding a process by name

pgrep nginx              # just the PIDs
pgrep -a nginx           # PIDs with command lines
pgrep -u www-data        # everything owned by a user
pidof nginx              # similar, space-separated

pgrep is the modern replacement for ps aux | grep name, and it avoids the classic annoyance of grep matching its own process — covered in the grep page.

top: watching it live

top -o %MEM         # start sorted by memory
top -u www-data     # one user only
top -p 1234,5678    # specific PIDs
top -b -n 1         # batch mode: print once and exit, for scripts

Once inside, single keypresses do the work:

KeyAction
MSort by memory
PSort by CPU
TSort by cumulative time
cToggle the full command line
1Show each CPU core separately
uFilter by user
kKill a process by PID
HShow individual threads
qQuit

Load average, properly explained

load average: 2.15, 1.87, 1.42

Three numbers: the average over the last 1, 5 and 15 minutes. This is the most misread figure in Linux administration.

It is not a percentage. It counts processes that are running or waiting to run — and on Linux, unlike most Unixes, it also counts processes blocked in uninterruptible I/O.

Compare it against your core count, which nproc gives you. On an 8-core machine, a load of 4 is comfortable and a load of 8 means fully committed. On a single-core machine, 4 means everything is queueing badly.

The three numbers together tell you the direction. 5.0, 2.0, 1.0 is a spike that just started. 1.0, 2.0, 5.0 is a problem that is resolving. And because I/O counts, a machine with load 12 and almost no CPU usage is not short of CPU — it is waiting on a disk.

Why %CPU can exceed 100

In top, 100% means one core fully used. A multi-threaded process on an 8-core machine can legitimately show 780%. Press 1 to see the cores individually, or Shift+I to switch to Irix mode, where the figure is divided by the core count.

htop: the one worth installing

sudo apt install htop        # Debian, Ubuntu
sudo dnf install htop        # Fedora, RHEL, Rocky, Alma
sudo pacman -S htop          # Arch

Same information, far better presented: per-core meters, colour, mouse support, scrolling, and a tree view on F5. F3 searches, F4 filters, F9 sends a signal with a menu rather than requiring you to remember numbers.

The reason to still know top is that it is installed everywhere, including on a stripped-down container or a machine you have no permission to change.

Stopping a process

kill 1234              # polite: sends SIGTERM
kill -9 1234           # forceful: sends SIGKILL
kill -HUP 1234         # often means "reload your configuration"
pkill nginx            # by name
pkill -u alice         # everything owned by a user
killall firefox        # by exact name
SignalNumberEffect
TERM15Asks the process to shut down cleanly. The default.
KILL9Kernel destroys it immediately. Cannot be caught or ignored.
HUP1Conventionally “reload config”, though it depends on the program.
INT2What Ctrl+C sends.
STOP / CONT19 / 18Pause and resume.

Do not reach for -9 first. SIGKILL gives the process no chance to flush buffers, finish writing files, remove lock files or close database connections. Corrupted data and stale locks are the usual reward. Send a plain kill, wait a few seconds, and escalate only if it is genuinely stuck.

And if -9 does not work either, the process is in D state and no signal will help. That is a hardware or filesystem problem.

For anything managed by systemd, use systemctl stop rather than killing it directly — otherwise systemd may simply restart it. See systemctl.

Priorities: nice and renice

nice -n 19 ./backup.sh      # start it at the lowest priority
renice -n 10 -p 1234        # lower an already-running process
sudo renice -n -5 -p 1234   # raise it — requires root

Niceness runs from -20 (greediest) to 19 (most generous), default 0. The name is the right way to remember it: a nicer process yields more readily. Only root can be less nice than the default.

This only affects CPU scheduling. A backup job saturating the disk will not be helped by nice — use ionice for that.

Gotchas

VSZ is not memory usage

Virtual size counts everything the process has mapped, including shared libraries, memory it reserved but never touched, and memory-mapped files. A JVM routinely shows tens of gigabytes of VSZ on a machine with 8GB of RAM. RSS is the number that matters, and even RSS double-counts shared libraries across processes — so adding up RSS for every process will exceed your actual usage.

High memory usage is usually fine

Linux uses free RAM for disk cache, so a healthy server shows almost no free memory. Look at the available column in free -h, not free. Cache is reclaimed instantly when something needs it. Empty RAM is wasted RAM.

The process you killed came back

Something is supervising it — systemd with Restart=always, a container runtime, or a process manager. Stop it at that level instead.

Killing a parent does not kill its children

Orphaned children are re-parented to init and keep running. Use pkill -P 1234 to target children, or kill -- -1234 to signal the whole process group (note the negative PID).

Quick reference

ps aux                        # everything, with resource usage
ps -ef                        # everything, with parent PIDs
ps -eo pid,rss,comm --sort=-rss | head    # top memory users
pgrep -a nginx                # find by name
pstree -p                     # process tree
top                           # live view (M memory, P cpu, 1 cores, q quit)
htop                          # better live view
kill 1234                     # ask nicely
kill -9 1234                  # last resort
pkill -u alice                # by user
nproc                         # core count, for reading load average
free -h                       # memory: read "available"

Related commands

  • systemctl — the right way to stop and start anything service-managed.
  • Disk space — when processes are stuck in D state, storage is often why.
  • awk — extracting PIDs and totals from ps output.
  • grep — and why pgrep is usually better for finding processes.
  • lsof — which files and sockets a process has open.
  • strace — what a hung process is actually asking the kernel to do.