Every Linux system is running dozens of processes at any moment. Knowing how to list them, inspect them, and kill them — without breaking anything else — is an essential sysadmin and developer skill. This guide covers everything from basic ps to job control, signals, systemctl, and /proc.
Quick reference
| Task | Command |
|---|---|
| List all running processes | ps aux |
| Show process tree | ps auxf or pstree -p |
| Find process by name | pgrep nginx or ps aux | grep nginx |
| Kill by PID | kill 1234 |
| Force-kill by PID | kill -9 1234 |
| Kill by name | pkill nginx |
| Interactive process monitor | top or htop |
| Run in background | command & |
| Send running process to background | Ctrl+Z, then bg |
| Bring background job to foreground | fg %1 |
| List background jobs | jobs |
| Keep running after logout | nohup command & |
| Show systemd service status | systemctl status nginx |
| Restart a service | sudo systemctl restart nginx |
| View process files/connections | lsof -p 1234 |
Listing processes with ps
ps (process status) is the standard tool for inspecting processes.
# All processes, BSD-style (most common)
ps aux
# COLUMNS: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# USER = owner, PID = process ID, STAT = state (S=sleeping, R=running, Z=zombie)
# All processes, UNIX-style
ps -ef
# Process tree (shows parent/child relationships)
ps auxf
# Processes for a specific user
ps -u www-data
# Show specific columns
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head -20
# Find process by name (case-insensitive)
ps aux | grep -i nginx
# Show a specific PID
ps -p 1234 -o pid,ppid,cmd,stat,%cpu,%mem
Process states (STAT column)
| Code | Meaning |
|---|---|
R |
Running or runnable |
S |
Interruptible sleep (waiting for event) |
D |
Uninterruptible sleep (usually I/O) |
T |
Stopped (by signal or job control) |
Z |
Zombie (finished but not reaped by parent) |
< |
High priority |
N |
Low priority (nice > 0) |
s |
Session leader |
l |
Multi-threaded |
+ |
In foreground process group |
Finding processes
# Find PID by name
pgrep nginx # returns PID(s)
pgrep -l nginx # returns PID and name
pgrep -a nginx # returns PID and full command
pgrep -u www-data # all processes by user
# Find with details
pidof nginx # PIDs of exact process name
# Full-text search across all processes
ps aux | grep '[n]ginx' # the [n] trick avoids matching grep itself
# Find what's using a port
ss -tlnp | grep :80
lsof -i :80
fuser 80/tcp # just the PID
# Find what process has a file open
lsof /var/log/nginx/access.log
# Find what files a process has open
lsof -p 1234
Sending signals with kill
kill sends a signal to a process — it does not have to mean "terminate."
# Send default signal (SIGTERM = graceful shutdown)
kill 1234
# Force kill (SIGKILL — cannot be caught or ignored)
kill -9 1234
kill -KILL 1234 # same thing
# Reload config without restart (SIGHUP)
kill -HUP 1234
kill -1 1234 # same thing
# Pause a process (SIGSTOP)
kill -STOP 1234
# Resume a paused process (SIGCONT)
kill -CONT 1234
# Kill by name
pkill nginx # SIGTERM to all matching
pkill -9 nginx # SIGKILL to all matching
pkill -HUP nginx # SIGHUP to all matching
pkill -u www-data # kill all processes by user
# Kill process group
kill -9 -1234 # negative PID = process group
Common signals
| Signal | Number | Default action | When to use |
|---|---|---|---|
SIGTERM |
15 | Terminate (graceful) | Default — always try this first |
SIGKILL |
9 | Terminate (forced) | Process won't respond to SIGTERM |
SIGHUP |
1 | Terminate (or reload) | Reload config (nginx, sshd, etc.) |
SIGINT |
2 | Terminate | Same as Ctrl+C |
SIGQUIT |
3 | Core dump | Debug crash |
SIGSTOP |
19 | Stop | Pause process |
SIGCONT |
18 | Continue | Resume paused process |
SIGUSR1 |
10 | User-defined | App-specific (e.g., reopen log files) |
SIGUSR2 |
12 | User-defined | App-specific |
Rule: Always try kill PID (SIGTERM) first. Give the process a few seconds to clean up. Only use kill -9 if it doesn't stop.
Interactive monitoring with top
top
Inside top:
| Key | Action |
|---|---|
q |
Quit |
k |
Kill (prompts for PID then signal) |
r |
Renice (change priority) |
M |
Sort by memory |
P |
Sort by CPU |
T |
Sort by time |
u |
Filter by user |
1 |
Show individual CPU cores |
H |
Show threads |
f |
Fields management |
d |
Change refresh interval |
W |
Save settings |
Useful top one-liners:
# Run top in batch mode (non-interactive, for scripting)
top -b -n 1 | head -20
# Show only one user's processes
top -u www-data
# Run for N iterations then exit
top -b -n 3 # 3 snapshots, good for scripts
htop
htop is top with a better interface. Install with apt install htop or yum install htop.
htop # interactive
htop -u www-data # filter by user
htop -p 1234,5678 # watch specific PIDs
htop -d 10 # 1-second refresh (delay in 10ths)
Job control
Job control lets you run multiple commands in one terminal — pause, background, and foreground them.
# Start a command in the background
sleep 300 &
# [1] 12345 ← job number and PID
# List background jobs
jobs
# [1]+ Running sleep 300 &
# [2]- Stopped vim file.txt
# Bring job 1 to foreground
fg %1
# Send foreground job to background
# 1. Press Ctrl+Z to pause it
# 2. Run:
bg %1
# Wait for all background jobs to finish
wait
# Disown a job (detach from terminal — won't die on logout)
disown %1
# Run something that continues after logout
nohup long-running-command > output.log 2>&1 &
Job spec syntax
| Spec | Refers to |
|---|---|
%1 |
Job number 1 |
%+ |
Current job (most recently stopped/backgrounded) |
%- |
Previous job |
%nginx |
Job whose command starts with "nginx" |
%?log |
Job whose command contains "log" |
Running processes that survive logout
# nohup: ignore SIGHUP (terminal close), redirect output
nohup python3 server.py > server.log 2>&1 &
# Check it's still running
jobs
ps aux | grep server.py
# screen: full terminal multiplexer
screen -S mysession # start named session
screen -ls # list sessions
screen -r mysession # reattach
# Inside screen: Ctrl+A then D to detach
# tmux (preferred over screen)
tmux new -s mysession
tmux ls
tmux attach -t mysession
# Inside tmux: Ctrl+B then D to detach
Process priority with nice and renice
Priority ranges from -20 (highest) to 19 (lowest). Default is 0. Regular users can only increase nice value (lower priority). Root can set any value.
# Start a process with lower priority (won't compete with system processes)
nice -n 10 python3 heavy-script.py
# Start with highest priority (root only)
sudo nice -n -20 critical-task
# Change priority of a running process
renice 15 -p 1234 # lower priority by PID
sudo renice -10 -p 1234 # raise priority (root only)
renice 5 -u www-data # change all www-data processes
Systemd and systemctl
Most modern Linux distros use systemd. systemctl manages services (daemons).
# Service lifecycle
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx # reload config without full restart
# Enable/disable on boot
sudo systemctl enable nginx
sudo systemctl disable nginx
sudo systemctl enable --now nginx # enable AND start immediately
# Status and logs
systemctl status nginx
journalctl -u nginx # all logs for nginx
journalctl -u nginx -f # follow (like tail -f)
journalctl -u nginx --since today
journalctl -u nginx --since "2026-07-14 10:00" --until "2026-07-14 11:00"
# List services
systemctl list-units --type=service
systemctl list-units --type=service --state=running
systemctl list-units --type=service --state=failed
# Check if service is active/enabled
systemctl is-active nginx
systemctl is-enabled nginx
# View unit file
systemctl cat nginx
sudo systemctl edit nginx # override unit file
Writing a simple systemd service
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/server.py
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload # after creating/editing unit file
sudo systemctl enable --now myapp
The /proc filesystem
/proc is a virtual filesystem exposing kernel and process information.
# Process-specific info (replace 1234 with actual PID)
cat /proc/1234/cmdline | tr '\0' ' ' # full command line
cat /proc/1234/status # process status and memory
cat /proc/1234/fd/ | ls -la # open file descriptors
cat /proc/1234/net/tcp # network connections
cat /proc/1234/maps # memory map
cat /proc/1234/environ | tr '\0' '\n' # environment variables
# System-wide info
cat /proc/cpuinfo # CPU details
cat /proc/meminfo # memory usage
cat /proc/uptime # system uptime in seconds
cat /proc/loadavg # load average (1, 5, 15 min + running/total processes)
cat /proc/version # kernel version
Resource monitoring
# CPU and memory overview
free -h # memory usage (human readable)
vmstat 1 5 # virtual memory stats (1s interval, 5 times)
mpstat 1 5 # per-CPU stats (needs sysstat)
iostat 1 5 # I/O stats
# Disk I/O by process
iotop -o # only processes doing I/O (needs root)
iotop -a # accumulated I/O
# Network bandwidth by process
nethogs eth0 # bandwidth per process
# Load average
uptime
# 14:23:01 up 5 days, load average: 0.15, 0.12, 0.10
# 1-min, 5-min, 15-min averages. Rule of thumb: < CPU count = OK
# Find memory hogs
ps aux --sort=-%mem | head -10
# Find CPU hogs
ps aux --sort=-%cpu | head -10
# Watch a command every 2 seconds
watch -n 2 'ps aux --sort=-%cpu | head -10'
Practical patterns
Pattern 1: Safely stop a stubborn process
PID=$(pgrep -f "myapp.py")
echo "Found PID: $PID"
# Try graceful first
kill "$PID"
sleep 3
# Check if still running
if kill -0 "$PID" 2>/dev/null; then
echo "Process still running, force-killing..."
kill -9 "$PID"
else
echo "Process stopped cleanly."
fi
Pattern 2: Run a script in the background, save PID
#!/bin/bash
nohup python3 worker.py > /var/log/worker.log 2>&1 &
echo $! > /var/run/worker.pid
echo "Started with PID $(cat /var/run/worker.pid)"
# Later, to stop it:
kill "$(cat /var/run/worker.pid)"
Pattern 3: Wait for a port to be available
wait_for_port() {
local port=$1
local retries=30
while ! ss -tlnp | grep -q ":${port} "; do
((retries--)) || { echo "Timeout waiting for port $port"; return 1; }
echo "Waiting for port $port..."
sleep 1
done
echo "Port $port is ready."
}
wait_for_port 5432 # wait for PostgreSQL
Pattern 4: Monitor a process and restart on crash
#!/bin/bash
# Simple process watchdog (prefer systemd Restart= in production)
while true; do
if ! pgrep -f "myapp.py" > /dev/null; then
echo "$(date): myapp.py not running, restarting..."
nohup python3 /opt/myapp/myapp.py >> /var/log/myapp.log 2>&1 &
fi
sleep 10
done
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
Going straight to kill -9 |
Process can't clean up (open files, DB connections left open) | Always try kill PID first, wait, then -9 |
Killing by name with pkill carelessly |
May kill unintended processes with similar names | Check with pgrep -l name before running pkill |
ps aux | grep name matches itself |
The grep process shows up in results | Use pgrep -a name or grep '[n]ame' pattern |
| Running heavy tasks at default priority | Competes with interactive processes | Use nice -n 10 command |
Not using nohup for background jobs |
Process dies when terminal closes | nohup command > output.log 2>&1 & |
| Assuming PID won't change | PID can be reused after process dies | Use pidfiles or re-run pgrep |
Managing services with raw kill instead of systemctl |
Service won't auto-restart, logs scattered | Use systemctl stop/restart on systemd systems |
ps vs top vs htop vs systemctl
| Tool | Best for |
|---|---|
ps aux |
Snapshot, scripting, grep-friendly |
ps auxf |
Seeing process tree relationships |
top |
Live monitoring, available everywhere |
htop |
Live monitoring with better UX (color, mouse, tree view) |
lsof -p PID |
What files/connections does this process have? |
systemctl status |
Status of a named service with recent logs |
journalctl -u service |
Full log history for a systemd service |
pgrep / pkill |
Scripted find/kill by name |
FAQ
Q: What's the difference between kill -9 and kill -15?-15 (SIGTERM) is a polite request — the process can catch it and clean up. -9 (SIGKILL) goes directly to the kernel; the process never sees it and cannot block it. Always use SIGTERM first.
Q: My process shows as D state (uninterruptible sleep). How do I kill it?
You often can't — it's waiting on I/O (usually a stuck NFS mount or failing disk). Even kill -9 won't work because the process is in kernel space. Fix the underlying I/O problem (unmount the NFS share, check disk health). A reboot is sometimes the only option.
Q: What is a zombie process and should I worry about it?
A zombie (state Z) is a process that has finished but whose parent hasn't called wait() to read its exit status. A few zombies are harmless. Many zombies usually indicate a buggy parent process. Killing the parent (or waiting for it to call wait()) cleans them up.
Q: How do I find which process is using the most memory?ps aux --sort=-%mem | head -10 gives a quick snapshot. For ongoing monitoring, htop sorted by MEM% is easiest. For RSS vs VSZ: RSS is actual physical RAM used; VSZ includes all virtual memory (often inflated by shared libraries).
Q: How do I run a cron job so it stays running even if it crashes?
Use systemd with Restart=on-failure in the unit file — that's the proper solution. Alternatively, combine cron with a lockfile check. For long-running daemons, systemd is always preferable to raw cron.
Q: What's the difference between kill -HUP and systemctl reload?kill -HUP PID sends SIGHUP directly to a specific PID. systemctl reload nginx also sends SIGHUP but only if the unit file defines ExecReload=. systemctl reload is safer because systemd tracks the outcome and updates service state; kill -HUP is a fire-and-forget signal.