Process Management in Linux: ps, top, kill, and jobs
Learn how to inspect, control, and kill processes on Linux — ps aux, top/htop, signals, foreground and background jobs, nohup, and systemctl basics — with runnable examples.
What you'll learn
- ✓How to list running processes with ps and filter the output
- ✓How to read top and htop without getting lost in the columns
- ✓What signals are and the difference between SIGTERM and SIGKILL
- ✓How to move jobs between the foreground and the background
- ✓How to keep a process running after you log out with nohup and &
- ✓How to start, stop, and check services with systemctl
Prerequisites
- •A working shell — see The Linux Terminal: A Beginner's Guide
- •Comfort with the commands from 20 Essential Linux Commands
Every running program on Linux is a process. The shell you type into is a process. The browser you read this in is a process. The cron daemon that runs nightly backups is a process. Knowing how to find them, watch them, and stop them is a core Linux skill — and the commands are simpler than they look.
What a process actually is
A process is a running instance of a program. The kernel gives each one a unique PID (process ID), tracks the user it runs as, the memory it holds, and the files it has open. Most processes have a parent — the process that started them — which is how bash ends up being the ancestor of nearly everything you launch from a terminal.
You will not need most of that detail day-to-day. What you do need:
- A way to see what is running (
ps,top,htop). - A way to stop something (
kill, signals). - A way to detach something so it keeps running (
&,nohup). - A way to manage services (
systemctl).
Listing processes with ps
ps prints a snapshot of currently running processes. On its own it shows only what is in your current shell:
ps
# PID TTY TIME CMD
# 12345 pts/0 00:00:00 bash
# 12678 pts/0 00:00:00 ps
That is rarely useful. The form everyone learns is ps aux:
ps aux
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 0.0 0.1 167284 11540 ? Ss Jun14 0:03 /sbin/init
# yash 1042 0.2 1.4 982140 58320 ? Sl Jun14 0:42 node server.js
# ...
What the columns mean:
- USER — who owns the process.
- PID — the unique process ID. This is what you pass to
kill. - %CPU / %MEM — recent CPU and memory share.
- STAT — process state (
Rrunning,Ssleeping,Zzombie,Tstopped). - START / TIME — when it started and how much CPU time it has used.
- COMMAND — the command line used to launch it.
In practice you almost always filter ps aux through grep:
ps aux | grep nginx
ps aux | grep -i postgres
A small annoyance: grep itself shows up in the results. The classic fix is a regex that does not match itself:
ps aux | grep "[n]ginx"
The brackets are a one-character character class — it matches n but the literal string in the grep command is [n]ginx, which does not match itself.
top and htop — live process monitors
ps is a snapshot. top is a live, refreshing view:
top
The header shows total CPU, memory, and load average. The body is a sortable list of processes — by default sorted by CPU usage, descending. Keyboard shortcuts inside top:
M— sort by memory.P— sort by CPU.k— kill a process (it will ask for a PID).q— quit.
htop is a much friendlier alternative — colour, mouse support, tree view, and easy scrolling. Install it once:
sudo apt install htop # Ubuntu/Debian
brew install htop # macOS
Then run:
htop
F5 toggles tree view (parent/child relationships), F6 lets you sort by any column, F9 sends a signal to the selected process. If you only learn one process-monitoring tool, learn htop.
Try it yourself. Open two terminals. In the first, run a long-lived command such as sleep 600. In the second, run htop, sort by CPU (F6), then find your sleep process by typing F3 and searching for sleep. Note its PID — you will use it below.
Signals: how processes are asked (or told) to stop
When you “kill” a process you do not literally erase it — you send it a signal. The kernel delivers the signal to the process, which gets to react (or not). The two signals you need now:
SIGTERM(signal 15) — the polite request. “Please clean up and exit.” Most well-behaved programs honour it: they close files, flush buffers, then exit. This is the default forkill.SIGKILL(signal 9) — the hard kill. The kernel terminates the process immediately. It gets no chance to clean up. Use only whenSIGTERMdoes not work.
There are others (SIGHUP to reload config, SIGINT which is what Ctrl+C sends, SIGSTOP/SIGCONT to pause and resume) but SIGTERM and SIGKILL cover ninety percent of real use.
Killing processes with kill and killall
Find the PID, send a signal:
ps aux | grep node
# yash 28412 0.2 1.4 982140 58320 ? Sl 09:11 0:42 node server.js
kill 28412 # send SIGTERM (default)
kill -15 28412 # same thing, explicit
kill -9 28412 # send SIGKILL — last resort
Always try a plain kill first. Only escalate to kill -9 if the process refuses to exit after several seconds. Programs killed with SIGKILL cannot flush their writes, which can corrupt files or leave stale lock files behind.
killall targets by name instead of PID:
killall node # SIGTERM every process named "node"
killall -9 firefox # hard-kill every Firefox process
This is convenient but blunt — make sure you do not have other node processes you care about.
pkill is similar but supports patterns:
pkill -f "server.js" # match against the full command line
Try it yourself. Start sleep 600 & in one terminal. In another, run ps aux | grep sleep to find its PID. Send it SIGTERM with kill <pid> and confirm with ps that it is gone. Repeat, this time using pkill -f "sleep 600".
Foreground and background jobs
When you run a command at the shell, it normally runs in the foreground — it takes over the terminal until it exits. Run it in the background by appending &:
sleep 60 &
# [1] 28941
The [1] is the job number (specific to your shell) and 28941 is the PID. The shell prompt comes back immediately and the sleep runs concurrently.
jobs lists your current shell’s background jobs:
jobs
# [1]+ Running sleep 60 &
Bring a background job back to the foreground with fg:
fg %1
Send a foreground job to the background with Ctrl+Z (which stops it) followed by bg:
sleep 60
# ^Z
# [1]+ Stopped sleep 60
bg %1
# [1]+ sleep 60 &
The pattern:
&— start in the background.Ctrl+Z— stop the foreground job.bg— resume a stopped job in the background.fg— pull a background or stopped job to the foreground.jobs— list them.
This is invaluable when you launch an editor like vim, realise you need the shell, hit Ctrl+Z to drop it to the background, do your shell work, then fg back to the editor.
Surviving a logout: nohup and disown
A background job started with & is still tied to your shell. When you log out, the shell sends SIGHUP to its children and they typically exit. To keep a process running after you log out, use nohup:
nohup ./long-job.sh > job.log 2>&1 &
What this does:
nohup— ignore theSIGHUPthat would otherwise kill it.> job.log 2>&1— redirect both stdout and stderr into a log file (otherwise they vanish on logout).&— run in the background.
If you already started something and forgot nohup, you can use disown:
./long-job.sh &
disown %1
For anything more elaborate — automatic restarts, log rotation, starting on boot — use systemd instead (see below). For one-off long-running tasks, nohup is perfect.
A more modern habit is to use tmux or screen and just leave a detached terminal session running. Same idea, more flexible. But nohup is always available, and that matters.
A whirlwind tour of systemctl
On any modern Linux system, services — long-running background daemons like nginx, postgresql, or ssh — are managed by systemd. The command-line tool is systemctl.
The four commands you actually use:
sudo systemctl status nginx # is it running? recent logs?
sudo systemctl start nginx # start it
sudo systemctl stop nginx # stop it
sudo systemctl restart nginx # stop then start
And the two you use less often but matter:
sudo systemctl enable nginx # start automatically on boot
sudo systemctl disable nginx # do not start on boot
systemctl status is the friendliest of the bunch — it shows whether the service is running, the PID, recent log lines, and any errors. It is the first thing to run when a service “is not working”:
sudo systemctl status postgresql
# ● postgresql.service - PostgreSQL RDBMS
# Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
# Active: active (running) since Sat 2026-06-14 09:11:02 UTC
For full service logs, use journalctl:
sudo journalctl -u nginx -n 100 --no-pager # last 100 lines for nginx
sudo journalctl -u nginx -f # follow live
Note that systemd is not present on macOS — these commands are Linux-only. macOS uses launchctl instead, but the daily concepts are the same.
A practical session
Suppose a Node app is misbehaving and using too much CPU. The full sequence:
# 1. Find the offender
ps aux | grep node
# yash 28412 95.0 1.4 982140 58320 ? Sl 09:11 12:42 node server.js
# 2. Confirm with a live view
htop # search for "node", read the CPU column
# 3. Ask nicely first
kill 28412
# 4. Confirm it exited
ps aux | grep "[n]ode"
# 5. If still there after a few seconds, escalate
kill -9 28412
For a system service the equivalent is:
sudo systemctl status myapp
sudo systemctl restart myapp
sudo journalctl -u myapp -n 50
Recap
You now know how to:
- List processes with
ps auxand filter withgrep. - Watch live activity with
topand (preferably)htop. - Send
SIGTERM(polite) withkillandSIGKILL(forceful) withkill -9— only as a last resort. - Kill by name with
killalland by pattern withpkill -f. - Run jobs in the background with
&, suspend withCtrl+Z, resume withbg, foreground withfg, list withjobs. - Keep a job alive past logout with
nohup ... > log 2>&1 &. - Start, stop, and inspect services with
systemctlandjournalctl.
Next steps
The next post turns these one-off commands into reusable programs — small bash scripts that automate the things you currently type by hand.
→ Next: Shell Scripting Basics — Your First Bash Script
Questions or feedback? Email codeloomdevv@gmail.com.