Five commands are enough to master Linux processes: ps, top, kill, nice and renice. With them, you will identify the process that hogs a CPU, adjust its priority, and stop it cleanly, without ever rebooting the server.
What you will learn
- List every active process with
ps auxandps -eo - Find a PID by name with
pgrepandpidof - Read the STAT codes (R, S, D, Z, T) and understand what they signal
- Monitor in real time with
top - Adjust the priority at launch (
nice) and on a running process (renice) - Stop cleanly: SIGTERM → wait → SIGKILL sequence
- Explore
/proc/$PID/statusandlsofto diagnose
In which context?
You need these commands as soon as a server behaves abnormally, and the symptom tells you which one to reach for first:
- a process has used 100 % of a CPU for several minutes,
- a service shows up several times in
ps aux(process leak), - a job is stuck in state
D(I/O wait) and no longer responds, - a
Zzombie piles up and never goes away, - you must stop a service cleanly before maintenance.
List processes with ps
Display every process
ps aux takes a snapshot of every process on the system at a given moment, those of other users included. It is the first move of a diagnosis, before filtering or sorting.
ps auxKey columns:
| Column | Meaning |
|---|---|
USER | Owner |
PID | Unique identifier |
%CPU | Percentage of CPU used |
%MEM | Percentage of memory |
STAT | State (see below) |
COMMAND | Command started |
Choose the columns with ps -eo
ps -eo pid,ppid,user,ni,stat,comm --sort=-%cpu | head -n 11--sort=-%cpu sorts by decreasing usage, the minus sign carrying the
inversion. head -n 11 keeps the header line plus the first ten
processes: with head -n 10 you would only get nine, since the header counts
as one line. The percentage shown is an average since the process started
(the ps manual calls it a "cputime/realtime" ratio), not an instant
measurement. Measured on a process that computed for five seconds then
sleeps: ps still shows 55 %, top shows 0.0 %. For the instant
value, top remains the right tool.
Read the STAT states
The STAT column is the first to check when a process "does
nothing". It combines an uppercase letter giving the kernel state, then
optional attribute characters. Two values call for action:
D signals an input/output wait that no signal interrupts, and Z
a process already finished that its parent has not reaped. The others are
normal operation, S being by far the most frequent on an idle
server.
| Code | Meaning |
|---|---|
R | Running |
S | Sleeping (interruptible) |
D | Blocked on I/O (uninterruptible) |
Z | Zombie (finished, parent has not read the exit code) |
T | Stopped (Ctrl+Z or SIGSTOP) |
s | Session leader |
+ | Foreground process |
l | Multi-threaded |
< | High priority (negative nice value) |
N | Low priority (positive nice value) |
Find a process by name
pgrep looks for a pattern in process names, not an exact name:
pgrep lee finds a sleep (measured). Add -x to require the full
name, and -a to display the command line. pidof, for its part,
expects the exact program name.
# By pattern in the name (-x for the exact name)pgrep nginx
# Several PIDs (several instances)pidof bash
# Pattern in ps with a self-excluding filterps aux | grep '[n]ginx'The [n]ginx trick excludes the line of grep itself: the bracketed pattern
does match the string nginx, but the command line of grep
contains the brackets and therefore does not match its own pattern. Without this
precaution, you always see at least one result, even when the service is
stopped.
Display the process tree
pstree draws the parent-child hierarchy of processes; the -p
option adds the PIDs. It belongs to the psmisc package, present in the
measured Debian 13 image but missing from the Ubuntu 24.04 and AlmaLinux 10
images: install this package if the command is missing.
pstree -pUseful to understand parent-child relationships, identify an orphan process, or see the threads of a service.
Monitor in real time with top
Where ps freezes a snapshot, top refreshes its display every three
seconds by default (two frames in 3.2 s, measured on Debian 13). This is the
difference that matters for a diagnosis: the %CPU column of top measures
the interval elapsed since the previous refresh, so it reflects the
current activity and not the lifetime average of the process. Measured with
procps-ng 4.0.4, this holds from the first frame: a process asleep
after five seconds of computing shows 0.0 % there, where ps keeps 55 %.
topUseful interactive keys:
| Key | Action |
|---|---|
P | Sort by CPU |
M | Sort by memory |
k | Send a signal to a process |
r | Change the priority (renice) |
q | Quit |
Execution priority: nice and renice
Each process has a nice value from −20 (high priority) to +19 (low priority). The default is 0. The higher it goes, the less the process is favoured by the scheduler.
Start a process with a reduced priority
nice -n sets the value at launch, before the process takes any CPU:
nice -n 15 tar czf archive.tar.gz /dataHandy for long jobs (backups, compilations) that must not disturb the other services.
Change the priority of a running process
renice acts on a process already running, without restarting it: it is what
saves a backup started without nice on a heavy-load day. Its output gives
the old and new value (old priority 3, new priority 5), and a
refusal shows clearly: renice: failed to set priority for 1234 (process ID): Permission denied, with exit code 1. Still frame the
command with two readings of the NI column: they are the proof that the
priority changed.
# Check the current valueps -o pid,ni,comm -p 1234
# Change the priorityrenice -n 10 -p 1234
# Check afterwardsps -o pid,ni,comm -p 1234Beware of a syntax trap: with the util-linux version shipped by
Debian, Ubuntu and RHEL (2.39 to 2.41), -n sets an absolute value. It is
not added to the current value: measured, renice -n 5 on a process at 3
gives 5, and renice --relative 2 then moves it to 7. The behaviour also
switches to relative when the POSIXLY_CORRECT variable is present in
the environment.
Send signals to a process
Common signals
A signal is a notification sent to a process by the kernel. Most
can be caught by the program, which then decides what to do with
it: this is exactly what lets nginx reload its configuration on
SIGHUP without dropping the connections in progress. Two exceptions in the table
below, SIGKILL and SIGSTOP, which the kernel applies without giving the
program the slightest chance to react. Remember the names rather than the numbers:
kill -TERM works everywhere, whereas the numbering varies with the
hardware architecture.
| Number | Name | Effect |
|---|---|---|
| 1 | SIGHUP | Configuration reload |
| 2 | SIGINT | Keyboard interrupt (Ctrl+C) |
| 15 | SIGTERM | Clean stop request (default) |
| 9 | SIGKILL | Immediate forced stop (cannot be caught) |
| 19 | SIGSTOP | Pause (cannot be caught) |
| 18 | SIGCONT | Resume after a pause |
Recommended sequence to stop a process
Ask politely first, check, and only then force: each step gives the process a chance to close its files cleanly.
# 1. Clean stop requestkill -TERM 1234
# 2. Wait a few seconds, then checksleep 3 && ps -p 1234
# 3. Force only if the process still does not respondkill -9 1234Kill by name with pkill and killall
Working by name avoids the round trip through pgrep to get a PID,
but the trade-off is real: the command hits every process that
matches, those of other users included. Get into the habit of
running pgrep -a <name> first to see the exact list of targets before
sending anything. Without privileges, each process out of your
reach produces pkill: killing pid … failed: Operation not permitted. Measured:
the exit code stays 0 when at least one process received the signal, it moves
to 1 when all were refused.
# First see what will be hit, with the full command linepgrep -a nginx
# Send SIGTERM to every process named "nginx"pkill -TERM nginx
# Restrict to your own processespkill -u "$USER" -TERM nginx
# Ask nginx to reread its configuration (PID of the master process)kill -HUP "$(cat /run/nginx.pid)"
# Kill every process with a given name (killall)killall -SIGTERM firefoxpkill looks for a pattern in the name, killall requires the full
command name by default. Practical consequence: pkill ngin reaches nginx,
killall ngin finds nothing. Both belong to different packages,
procps for pkill and psmisc for killall, and killall is not
always installed: measured, it is missing from the Ubuntu 24.04 and AlmaLinux 10 images.
For a service managed by systemd, prefer systemctl reload to a
kill -HUP: systemd knows which process to target.
Explore /proc to diagnose
Linux exposes the information of each process in /proc/<PID>/, a virtual directory the kernel fills on the fly: it is the source that ps and top read themselves.
# Card of the current processcat /proc/$$/status
# Extract the useful fieldsgrep -E "^(Name|Pid|PPid|State|VmRSS)" /proc/1234/statusSample output:
Name: nginxState: S (sleeping)Pid: 1234PPid: 1VmRSS: 8560 kBList the files opened by a process
lsof (list open files) lists the files, sockets and libraries
a process keeps open. It was installed in none of the three measured
images: the package is called lsof everywhere. Without it, ls -l /proc/1234/fd
already gives the list of open descriptors.
lsof -p 1234Or to find which process uses a port, with ss as an alternative
(iproute2 package, installed on any server):
lsof -i :80sudo ss -tlnp 'sport = :80'Troubleshooting
The six situations below cover almost every call received on
this topic. Read them in the symptom then cause direction before applying the
solution: the first two lines look alike on screen but have nothing
in common. A process that kill -9 does not reach is a hardware or network
mount problem, not a signal problem; resending the signal will
change nothing.
| Symptom | Likely cause | Solution |
|---|---|---|
kill -9 1234 does not kill the process | State D (I/O blocked, uninterruptible) | Diagnose the I/O subsystem (disk, NFS) |
pkill nginx finds nothing | Command name differs from the binary | Use pgrep to confirm the exact name |
renice refused with Permission denied | Non-root user lowering the nice value, even from 10 to 5 | Use sudo renice |
pstree or killall not found | psmisc package missing | Install psmisc |
Z zombie that never goes away | The parent process has not called wait() | Find the parent with ps -o ppid -p $ZOMBIE, kill the parent if possible |
top no longer refreshes | Frozen terminal | q or Ctrl+C, then start again |
Key points
ps auxlists every process;ps -eochooses the columns.pgrep <pattern>(orpgrep -x <name>for the exact name) andps aux | grep '[n]ame'find a PID by name.- The STAT codes (
R,S,D,Z,T) give the state,DandZdeserve attention. nice -n N command(priority at launch);renice -n N -p PID(after start, absolute value).- Always SIGTERM first, wait, then SIGKILL only if needed.
/proc/$PID/statusdiagnoses without any tool to install;lsof -p $PIDcompletes it once thelsofpackage is installed.
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Jobs and job control in the shell
A new full quiz with random questions
Work again only on the questions you missed