Every running program in Linux is a process. Understanding how to view, control, and manage processes is essential for system administration, performance tuning, and DevOps operations. When a web server stops responding, when a script runs out of control, when you need to free up CPU — process management is the skill you reach for.
When you run a program, Linux creates a process — an instance of that program running in memory with its own PID (Process ID), memory space, and CPU allocation. Every process has a parent process. The very first process, init or systemd, has PID 1 and is the ancestor of all others.
ps aux # All processes — snapshot
ps aux | grep nginx # Find specific process
top # Real-time process monitor (q to quit)
htop # Better interactive process monitor
pstree # Process tree — parent/child relationships
pgrep nginx # Get PID of process by name
pidof python3 # PID of running Python
USER PID %CPU %MEM VSZ RSS STAT START TIME COMMAND
root 1 0.0 0.1 169936 13248 Ss Jan01 0:05 /sbin/init
www-data 1234 2.1 1.5 256789 45678 S 10:30 0:23 nginx: worker
Key columns: PID (process ID), %CPU (CPU usage), %MEM (memory usage), STAT (S=sleeping, R=running, Z=zombie), TIME (total CPU time used).
kill 1234 # Send SIGTERM — graceful shutdown
kill -9 1234 # Send SIGKILL — force kill immediately
kill -HUP 1234 # Send SIGHUP — reload configuration
pkill nginx # Kill by name
killall python3 # Kill all processes with this name
kill -l # List all signal names
Always try graceful SIGTERM first. SIGKILL (-9) is the last resort — it gives the process no chance to clean up, which can leave files in inconsistent states or leave database transactions incomplete.
python app.py & # Run in background
Ctrl+C # Kill foreground process
Ctrl+Z # Suspend foreground process
jobs # List background jobs
fg 1 # Bring job 1 to foreground
bg 1 # Resume job 1 in background
nohup python app.py & # Run immune to hangup (survives logout)
disown -h %1 # Detach job from shell
nice -n 10 python heavy_task.py # Start with low priority
nice -n -5 critical_task # Start with higher priority (needs root)
renice 15 -p 1234 # Change priority of running process
renice -5 -p 1234 # Increase priority (needs root)
Nice values range from -20 (highest priority) to 19 (lowest). Default is 0. Use high nice values for background tasks that should not compete with important processes.
free -h # Total memory overview
cat /proc/meminfo # Detailed memory stats
ps aux --sort=-%mem | head -10 # Top 10 memory consumers
cat /proc/1234/status # Memory usage of specific process
ps aux --sort=-%cpu | head -10 # Top 10 CPU consumers
mpstat 1 5 # CPU stats every second, 5 times
uptime # Load averages (1, 5, 15 minutes)
In production environments, processes behave unexpectedly. A memory leak slowly consumes all available RAM until the system kills processes randomly. A runaway script pins the CPU at 100% and makes everything else slow. A zombie process accumulates and exhausts the process table. Understanding how to identify, analyze, and resolve these situations is what separates a reactive engineer from a proactive one.
Good process management skills also feed directly into capacity planning — understanding normal CPU and memory usage patterns helps you decide when to scale up infrastructure and when to optimize application code first.
In Part 6, we will cover networking — how Linux connects to the world and how to troubleshoot network issues from the command line.
Linux processes communicate through signals — notifications sent from the kernel, from other processes, or from users. The most important signals: SIGTERM (15) is the polite termination request — it asks the process to clean up and exit gracefully. SIGKILL (9) is the immediate, unblockable kill — the kernel terminates the process without giving it a chance to clean up. Use only when SIGTERM does not work. SIGHUP (1) is interpreted by many daemon processes as a request to reload configuration — useful for applying changes without restarting. SIGINT (2) is what Ctrl+C sends. Use kill -SIGNAL PID to send signals, or kill -l to list all available signals.
The load average visible in top and uptime represents the average number of processes that are running or waiting for CPU time, measured over 1, 5, and 15 minute intervals. A load average equal to the number of CPU cores means the system is fully utilized. Above that means processes are waiting for CPU — the system is overloaded. Use nproc to see your core count. On a 4-core system, a load average of 2.0 means 50% utilization. A load average of 6.0 on that same system means the CPU is overloaded and processes are queuing. Always interpret load average relative to available cores, not as an absolute number.
Linux shells support job control — managing multiple processes from a single terminal. Run a command in the background by appending &: sleep 300 &. Use jobs to list background jobs with their job numbers. Bring a background job to the foreground with fg %1 (where 1 is the job number). Send a foreground job to the background by pressing Ctrl+Z (which pauses it) then running bg %1. The nohup command runs a process that continues after you log out: nohup long_script.sh &. For long-running processes on remote servers, use tmux or screen to create persistent terminal sessions that survive disconnection.
Run several background processes: sleep 1000 & three times. Then use ps aux | grep sleep to find them, jobs to list them, and practice sending signals: kill -15 PID to one and kill -9 PID to another. Also try killall sleep to kill all sleep processes at once. Run top and observe the load average, CPU usage, and memory usage. Press P to sort by CPU and M to sort by memory. Press k to kill a process from within top.