Every running program on a Linux system is a process. Whether it is the shell you are typing commands into, a web server handling thousands of requests, or a database crunching queries, it is all processes under the hood. Understanding how to inspect, control, prioritize, and terminate processes is a core sysadmin skill and a major topic on the LPIC-1 certification exams.
This guide covers everything you need to know about Linux process management, from the fundamentals through advanced techniques. Every command shown here was tested on systems running recent kernels (6.x series) with systemd, but the concepts apply across all major distributions.
1. What Is a Process?
A process is an instance of a running program. When you launch /usr/bin/vim, the kernel allocates memory, assigns a unique identifier, and begins executing the binary. That running instance is a process. The binary sitting on disk is just a file; the process is the living, breathing execution of that file.
Key Process Attributes
PID (Process ID) is the unique integer the kernel assigns to every process. PIDs start at 1 (which belongs to the init system, typically systemd on modern distributions) and increment from there. The kernel recycles PIDs after a process exits, but the maximum PID value is configurable via /proc/sys/kernel/pid_max.
PPID (Parent Process ID) tracks which process spawned the current one. Every process (except PID 1) has a parent. When you run ls from your bash shell, the bash process forks a child, and that child’s PPID is your shell’s PID. This parent/child relationship forms a tree structure you can visualize with pstree.
UID/GID determine what the process is allowed to do. Processes inherit the user and group identity of the account that started them, unless special mechanisms like setuid bits or capabilities alter that.
Process States
At any given moment, a process is in one of several states. You will see these as single-letter codes in tools like ps and top:
- R (Running) : The process is either executing on a CPU core right now or sitting in the run queue ready to execute.
- S (Sleeping) : The process is waiting for an event, such as I/O completion or a timer. This is the most common state for idle services.
- D (Uninterruptible Sleep) : The process is waiting for I/O (usually disk) and cannot be interrupted. Processes stuck in D state often indicate storage or NFS problems.
- T (Stopped) : The process has been paused, typically by a SIGSTOP or SIGTSTP signal (Ctrl+Z from the terminal).
- Z (Zombie) : The process has finished execution but its parent has not yet read its exit status. More on this later.
- I (Idle) : A kernel thread state introduced in Linux 4.14, representing kernel threads that are idle.
Foreground vs Background Processes
A foreground process holds the terminal. You cannot type another command until it finishes or you suspend it. A background process runs without holding the terminal, letting you keep working. Every sysadmin needs to move processes between foreground and background routinely. We cover the mechanics in Section 5.
2. The ps Command: Deep Dive
The ps command produces a snapshot of current processes. It does not update in real time (unlike top). There are two major syntax styles: BSD style (no leading dash) and UNIX/POSIX style (with dashes). Both are valid and you will see both in the wild.
ps aux: The Classic BSD-Style Listing
This is the single most commonly used ps invocation. It shows all processes from all users with a human-readable format.
ps aux
Sample output (truncated):
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 171636 13292 ? Ss Mar15 0:05 /usr/lib/systemd/systemd
root 2 0.0 0.0 0 0 ? S Mar15 0:00 [kthreadd]
www-data 4521 2.3 1.4 524288 58432 ? Sl 09:12 1:42 /usr/sbin/apache2 -k start
jdoe 7891 0.0 0.2 22456 8320 pts/0 Ss 10:30 0:00 -bash
jdoe 8024 0.0 0.0 10080 3200 pts/0 R+ 11:05 0:00 ps aux
The columns break down as follows. USER is the process owner. PID is the process ID. %CPU and %MEM show resource usage. VSZ is virtual memory size in KB. RSS is resident set size (actual physical memory used) in KB. TTY is the controlling terminal (? means no terminal, common for daemons). STAT is the process state plus modifiers (s = session leader, + = foreground process group, l = multi-threaded). START is when the process launched. TIME is cumulative CPU time. COMMAND is the full command line.
ps -ef: The POSIX-Style Listing
This is the POSIX equivalent and is equally common.
ps -ef
Sample output:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Mar15 ? 00:00:05 /usr/lib/systemd/systemd
root 2 0 0 Mar15 ? 00:00:00 [kthreadd]
www-data 4521 4500 2 09:12 ? 00:01:42 /usr/sbin/apache2 -k start
The key difference is that ps -ef includes the PPID column, which is useful when you need to trace parent/child relationships.
Custom Output with ps -eo
The -o flag lets you select exactly which columns you want. This is powerful for scripting and targeted troubleshooting.
ps -eo pid,ppid,user,nice,stat,%cpu,%mem,comm --sort=-%cpu | head -15
That command lists processes sorted by CPU usage (descending), showing only the fields you care about. The --sort flag accepts any column name; prefix with - for descending order.
Another practical example shows process start time and elapsed time:
ps -eo pid,user,lstart,etime,comm --sort=-etime | head -20
Filtering Processes
Filter by username with -u:
ps -u www-data -o pid,%cpu,%mem,comm
Filter by process name with -C:
ps -C nginx -o pid,ppid,%cpu,%mem,stat
Filter by PID with -p:
ps -p 1,4521,7891 -o pid,user,comm,stat
Piping through grep is another common approach. Use brackets around the first character to avoid matching the grep process itself:
ps aux | grep '[n]ginx'
Process Trees with ps
Visualize the parent/child hierarchy:
ps auxf
Or use the dedicated tree command for a cleaner view:
pstree -p
The -p flag shows PIDs alongside the tree structure. Add -u to see UID transitions (useful for spotting privilege changes).
3. Real-Time Monitoring: top and htop
top: The Built-In Monitor
While ps gives you a snapshot, top gives you a live, updating view. Launch it with:
top
The header area shows system-wide stats: uptime, load averages, total tasks broken down by state, CPU usage split into user/system/nice/idle/iowait/irq/softirq/steal, and memory/swap usage.
Key interactive commands while top is running:
- P : Sort by CPU usage (default).
- M : Sort by memory usage.
- T : Sort by cumulative time.
- k : Kill a process. It prompts for the PID and the signal to send.
- r : Renice a process. Prompts for PID and new nice value.
- u : Filter to show only one user’s processes.
- c : Toggle between showing the command name and the full command line.
- 1 : Toggle per-CPU core breakdown (extremely useful on multi-core systems).
- H : Toggle thread display.
- f : Open the field management screen to add, remove, or reorder columns.
- W : Write your current settings to
~/.toprcso they persist. - q : Quit.
You can also launch top in batch mode for scripting. This runs it once and exits:
top -bn1 | head -20
Filter by a specific user from the command line:
top -u postgres
htop: The Better Interactive Monitor
The htop utility is a more user-friendly alternative. Install it if it is not already present:
## Debian/Ubuntu
sudo apt install htop
## RHEL/Fedora/Rocky
sudo dnf install htop
Launch it with:
htop
What makes htop better for day-to-day work:
- Color-coded CPU and memory bars at the top give you an instant visual read on system load.
- Mouse support. You can click column headers to sort, click processes to select them, and use the function key bar at the bottom.
- Built-in tree view. Press F5 to toggle the process tree.
- Incremental search. Press F3 (or /) and start typing to find a process by name.
- Filter. Press F4 to filter the process list to show only matching entries.
- Multiple sort options. Press F6 to choose the sort column from a sidebar.
- You can send signals without memorizing numbers. Press F9 to bring up a signal selection menu.
- Press F2 to open the setup screen where you can customize meters, colors, and layout. Changes save to
~/.config/htop/htoprc.
For servers where you prefer a terminal-based dashboard, htop is the go-to tool. On headless systems, it is usually one of the first packages I install.
4. Process Priority: nice and renice
The Linux scheduler decides which processes get CPU time. You can influence this decision using nice values. Nice values range from -20 (highest priority, least “nice” to other processes) to 19 (lowest priority, most “nice”). The default nice value for a new process is 0.
Only root can set negative nice values (raise priority). Regular users can only make their processes nicer (lower priority), not less nice.
Starting a Process with a Custom Nice Value
Use the nice command to launch a process at a specific priority:
nice -n 10 tar czf /backup/archive.tar.gz /home
That runs the backup at a lower priority so it does not starve interactive users of CPU. This is a classic use case: long-running batch work that should yield to interactive workloads.
To start something at maximum priority (as root):
sudo nice -n -20 /opt/critical-app/run.sh
Changing Priority of a Running Process
Use renice to adjust the priority of an already-running process:
renice -n 15 -p 4521
You can also renice all processes belonging to a user:
sudo renice -n 5 -u www-data
Check a process’s current nice value:
ps -o pid,ni,comm -p 4521
The NI column in top and htop also shows the nice value. Remember that nice values affect CPU scheduling only. They have no effect on I/O priority. For I/O scheduling priority, look into ionice.
5. Background and Foreground Process Control
Managing foreground and background processes is something you will do dozens of times a day in a terminal. Here is the full workflow.
Running a Process in the Background
Append & to any command to run it in the background immediately:
rsync -av /data/ /backup/data/ &
The shell prints the job number and PID:
[1] 12345
Suspending a Foreground Process
Press Ctrl+Z to suspend (stop) the current foreground process. It does not terminate; it pauses. You will see output like:
[1]+ Stopped rsync -av /data/ /backup/data/
jobs, bg, and fg
List all jobs in the current shell session:
jobs -l
The -l flag includes PIDs in the listing. Sample output:
[1]+ 12345 Stopped rsync -av /data/ /backup/data/
[2]- 12400 Running find / -name "*.log" > /tmp/logs.txt &
Resume a stopped job in the background:
bg %1
Bring a background job to the foreground:
fg %1
The %1 refers to job number 1. You can also use % followed by a string that matches the command name, like fg %rsync.
Surviving Logout: nohup and disown
Background processes normally receive a SIGHUP signal when you close the terminal, which terminates them. Two tools prevent this.
The nohup command makes a process immune to SIGHUP from the start:
nohup ./long-running-script.sh &
Output that would normally go to the terminal is redirected to nohup.out (or you can redirect it explicitly):
nohup ./long-running-script.sh > /var/log/myscript.log 2>&1 &
If you forgot to use nohup and the process is already running, use disown:
disown %1
After disown, the job is removed from the shell’s job table and will not receive SIGHUP when the shell exits. This is a lifesaver when you realize mid-task that you should have used nohup or a terminal multiplexer.
For anything long-running on a production server, consider tmux or screen instead. They give you a persistent session you can detach from and reattach to later.
6. Sending Signals: kill, killall, and pkill
Signals are the kernel’s mechanism for communicating with processes. You send signals to tell a process to terminate, reload its configuration, dump a core, or take other actions.
Common Signals
The most important signals every admin should know:
- SIGTERM (15) : Graceful termination. The process receives the signal and has the opportunity to clean up (close files, remove temp files, finish transactions) before exiting. This is the default signal sent by
kill. - SIGKILL (9) : Forceful termination. The kernel immediately removes the process. The process gets no chance to clean up. Use this only when SIGTERM does not work. It cannot be caught, blocked, or ignored by the process.
- SIGHUP (1) : Historically meant “hangup” (as in a modem disconnection). Many daemons interpret SIGHUP as an instruction to reload their configuration files without restarting. For example, sending SIGHUP to Nginx causes it to re-read nginx.conf.
- SIGINT (2) : This is what Ctrl+C sends. It is an interrupt signal requesting the process to terminate.
- SIGSTOP (19) : Pauses the process. Like SIGKILL, it cannot be caught or ignored. The process freezes until it receives SIGCONT.
- SIGCONT (18) : Resumes a stopped process.
- SIGUSR1 (10) and SIGUSR2 (12) : User-defined signals. Applications use these for custom behavior. For instance, sending SIGUSR1 to some applications triggers a log rotation or status dump.
List all available signals on your system:
kill -l
kill: Send a Signal by PID
The kill command sends a signal to a specific PID. Despite the name, it does not always kill processes; it sends whatever signal you specify.
## Send SIGTERM (default, graceful)
kill 4521
## Send SIGKILL (forceful)
kill -9 4521
## Send SIGHUP (reload config)
kill -HUP 4521
## Send using signal name
kill -SIGTERM 4521
The recommended approach is to always try SIGTERM first. Give the process a few seconds, then check if it is still running. Only escalate to SIGKILL if necessary.
killall: Send a Signal by Process Name
The killall command targets all processes matching a given name:
killall nginx
Be careful with killall. It matches by the process name exactly. If you have multiple services with workers sharing a name, you might terminate more than intended. You can add -i for interactive mode, which prompts for confirmation on each match:
killall -i python3
Send SIGHUP to reload all instances:
killall -HUP nginx
pkill: Send a Signal with Pattern Matching
The pkill command is more flexible than killall because it uses pattern matching (regex):
## Kill processes matching a pattern
pkill -f "python3 /opt/myapp/server.py"
## Kill by user
pkill -u jdoe
## Kill by user AND pattern
pkill -u jdoe python3
The -f flag matches against the full command line rather than just the process name. This is extremely useful when you have multiple Python scripts running and need to target a specific one.
7. Process Monitoring Tools
watch: Repeat a Command at Intervals
The watch command runs any command repeatedly and displays the output in a full-screen view. It is perfect for monitoring processes over time.
watch -n 2 'ps -eo pid,%cpu,%mem,comm --sort=-%cpu | head -15'
That refreshes every 2 seconds, showing the top 15 CPU consumers. Add -d to highlight differences between updates:
watch -d -n 5 'ps aux | wc -l'
The /proc Filesystem
Every running process has a directory under /proc named after its PID. This virtual filesystem is the kernel’s window into process internals.
ls /proc/1/
Useful files inside /proc/[PID]/:
- cmdline : The full command line used to start the process.
- status : Human-readable status including name, state, PID, PPID, UID, memory usage, and more.
- fd/ : Directory of symbolic links to all open file descriptors.
- environ : The environment variables the process was started with.
- maps : Memory mappings.
- limits : Current resource limits (soft and hard) for the process.
- io : I/O statistics (bytes read/written).
Check what a process’s current resource limits are:
cat /proc/4521/limits
Count how many file descriptors a process has open:
ls /proc/4521/fd | wc -l
Read a process’s current status:
cat /proc/4521/status
pidof and pgrep
Find the PID of a running program by its name:
pidof sshd
Sample output:
985 22410 22450
The pgrep command is more flexible and supports pattern matching and various filters:
## Find PIDs by name pattern
pgrep -a nginx
## Find PIDs owned by a user
pgrep -u postgres
## Count matching processes
pgrep -c apache2
## Show only the newest match
pgrep --newest sshd
The -a flag shows the full command line alongside the PID, which is helpful when you need to distinguish between multiple instances of the same binary.
8. Managing Service Processes with systemctl
On any modern Linux system running systemd, services (long-running daemons) are managed through systemctl. This is the standard interface for starting, stopping, and inspecting service processes.
## Check if a service is running
systemctl status nginx
## Start a service
sudo systemctl start nginx
## Stop a service
sudo systemctl stop nginx
## Restart (stop then start)
sudo systemctl restart nginx
## Reload configuration without full restart
sudo systemctl reload nginx
## Enable a service to start at boot
sudo systemctl enable nginx
## Disable a service from starting at boot
sudo systemctl disable nginx
The systemctl status output is rich. It shows the service’s active state, the main PID, memory usage, recent log lines, and the cgroup the service belongs to.
List all running services:
systemctl list-units --type=service --state=running
List all failed services (useful when troubleshooting after a reboot):
systemctl list-units --type=service --state=failed
See the full logs for a service:
journalctl -u nginx -f
The -f flag follows the log in real time, similar to tail -f.
9. cgroups and Resource Limits
Control groups (cgroups) are a kernel feature that lets you allocate, limit, and monitor system resources (CPU, memory, I/O, network) for groups of processes. Every systemd service automatically runs inside its own cgroup, which is why systemctl status can show per-service memory usage.
Viewing cgroup Information
See which cgroup a process belongs to:
cat /proc/4521/cgroup
View resource usage for a systemd service slice:
systemd-cgtop
The systemd-cgtop command works like top but shows resource usage organized by cgroup (which maps to services, slices, and scopes in systemd).
Setting Resource Limits on Services
You can cap the resources a service is allowed to consume by editing its systemd unit file or using an override:
sudo systemctl edit nginx
In the editor, add resource limits under the [Service] section:
[Service]
MemoryMax=512M
CPUQuota=50%
TasksMax=200
MemoryMax caps the total memory the service and all its children can use. If the limit is hit, the kernel’s OOM killer terminates processes in that cgroup. CPUQuota limits CPU time as a percentage (100% equals one full core). TasksMax limits the number of tasks (processes and threads) the service can spawn, which is a good defense against fork bombs.
After saving, reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart nginx
Per-User and Per-Session Limits with ulimit
Traditional resource limits set via ulimit still apply and are still relevant. View current limits:
ulimit -a
Common limits you might adjust:
## Max open files for current session
ulimit -n 65535
## Max user processes
ulimit -u 4096
For persistent limits, edit /etc/security/limits.conf or drop a file in /etc/security/limits.d/.
10. Zombie and Orphan Processes
These are two special cases in process lifecycle management that come up in exams and in real-world troubleshooting.
Zombie Processes
A zombie (or defunct) process is one that has finished executing but still has an entry in the process table. This happens because the parent process has not yet called wait() to read the child’s exit status. The zombie consumes no CPU or memory beyond its entry in the process table, but each one takes up a PID slot.
Find zombie processes:
ps aux | awk '$8 ~ /^Z/ {print}'
Or more simply:
ps -eo pid,ppid,stat,comm | grep -w Z
You cannot kill a zombie directly. It is already dead. The proper fix is to make the parent process call wait(). You can nudge the parent by sending it SIGCHLD:
kill -SIGCHLD <parent_pid>
If the parent ignores SIGCHLD or is buggy, your options are:
- Kill the parent process. When the parent dies, the zombies become orphans and are adopted by PID 1 (systemd), which will reap them.
- Fix the parent application’s code so it properly handles child process exit.
A few zombies are not a problem. Thousands of them indicate a broken application that is not cleaning up its children.
Orphan Processes
An orphan process is one whose parent has exited while the child is still running. The kernel automatically re-parents orphans to PID 1 (the init system). On systemd-based systems, systemd takes over as the parent and will properly reap these processes when they exit.
Orphan processes are generally not a problem in normal operation. They become noteworthy when a service’s main process crashes and leaves worker children running without supervision. Find processes whose parent is PID 1 but probably should not be:
ps -eo pid,ppid,user,comm | awk '$2 == 1 && $3 != "root"'
That shows non-root processes parented to PID 1, which might be orphaned workers worth investigating.
11. Useful One-Liners for Daily Work
These are commands I keep in my back pocket for quick troubleshooting. Each one solves a common question you will face as a sysadmin.
Top 10 Memory Consumers
ps -eo pid,user,%mem,rss,comm --sort=-%mem | head -11
Top 10 CPU Consumers
ps -eo pid,user,%cpu,comm --sort=-%cpu | head -11
Full Process Tree
pstree -pua
The flags: -p shows PIDs, -u shows UID transitions, -a shows command line arguments.
Count Processes Per User
ps -eo user= | sort | uniq -c | sort -rn | head -10
Find All Processes Using a Specific Port
ss -tlnp | grep ':80 '
Find Processes with High Open File Descriptor Count
for pid in /proc/[0-9]*/fd; do
count=$(ls "$pid" 2>/dev/null | wc -l)
if [ "$count" -gt 100 ]; then
p=$(echo "$pid" | cut -d/ -f3)
echo "$count $p $(cat /proc/$p/comm 2>/dev/null)"
fi
done | sort -rn | head -10
Watch for Zombie Processes in Real Time
watch -n 5 'ps -eo stat | grep -c Z'
Show Processes That Have Been Running the Longest
ps -eo pid,etime,user,comm --sort=-etime | head -15
Kill All Processes Belonging to a User
sudo pkill -u jdoe
Or the nuclear option that also forces logout:
sudo loginctl terminate-user jdoe
12. LPIC Exam Relevance
Process management is heavily tested on the LPIC-1 certification (exams 101 and 102). Specifically, these topics map to the following exam objectives:
- Topic 103.5: Create, monitor and kill processes (Exam 101, weight 4). This covers
ps,top,kill,killall,bg,fg,jobs,nohup,watch,free,uptime, andscreen/tmux. - Topic 103.6: Modify process execution priorities (Exam 101, weight 2). This covers
nice,renice, and understanding the nice value range.
For the LPIC-1, focus especially on these areas:
- Know the signal numbers. The exam expects you to know that SIGTERM is 15, SIGKILL is 9, and SIGHUP is 1. Know that the default signal for
killis SIGTERM. - Understand the difference between
kill,killall, andpkill. Particularly,killtakes a PID,killalltakes an exact name, andpkilltakes a pattern. - Know the nice value range. -20 to 19. Regular users can only increase nice values (lower priority). Root can set any value.
- Understand job control syntax.
Ctrl+Zto suspend,bgto background,fgto foreground,&to start in background,jobsto list. - Know
nohup. What it does, where its default output goes (nohup.out), and when you would use it. - Read ps output. You should be able to look at ps output and identify process states, the parent PID, CPU/memory usage, and the controlling terminal.
- Process states. Know what R, S, D, T, and Z mean. The exam might give you a scenario and ask what state a process is likely in.
For the LPIC-2, you will also need to understand cgroups, resource limits, and deeper systemd service management, which we touched on in Sections 8 and 9.
Summary
Process management in Linux boils down to four activities: inspecting what is running, controlling how it runs, adjusting its priority, and terminating it when needed. The tools covered here give you complete control over every process on the system.
For quick reference:
- Snapshot:
ps aux,ps -ef,ps -eo - Live monitoring:
top,htop,watch - Finding processes:
pgrep,pidof,/proc - Priority:
nice,renice - Job control:
bg,fg,jobs,&,Ctrl+Z,nohup,disown - Signals:
kill,killall,pkill - Services:
systemctl,journalctl - Resource control: cgroups,
ulimit,systemd-cgtop
Master these tools and you will handle any process-related situation that comes up, whether on an exam or a production server at 3 AM.































































