Linux Process Management: ps, kill, nice, and systemd

Linux Process Management: ps, kill, nice, and systemd

Tested on: Ubuntu 26.04 LTS · Debian 12 · Fedora 44 · Arch Linux — Last updated: June 2026

Every program running on your system is a process. Linux gives you complete visibility and control over those processes — inspect them, adjust their priority, send signals, confine their resources, or tie them to systemd for supervised execution. This guide covers the full toolkit: ps, top, htop, kill, nice, systemctl, strace, lsof, and cgroups.

Contents
  1. Prerequisites
  2. What Is a Process
  3. ps — Snapshot of Processes
    1. Reading ps Output
  4. top and htop — Real-Time Monitoring
  5. Signals and kill
    1. Signal Reference
  6. Process Priority with nice and renice
  7. Background and Foreground Jobs
  8. systemd Service Management
    1. Further Reading

Prerequisites

  • A Linux system running systemd (Ubuntu 24.04+, Debian 12+, Fedora 33+, or equivalent)
  • Basic terminal familiarity — you know how to open a shell and run commands
  • sudo access for signal operations on other users' processes and systemd management
  • Optional: htop, strace, lsof, smartmontools — install instructions are included inline

What Is a Process

A process is a running instance of a program, complete with its own memory space, file descriptors, and kernel-assigned Process ID (PID). When you type ls, the shell forks a child process, the kernel loads the ls binary into it, executes it, and tears the process down when it exits. Long-running programs — web servers, databases, daemons — persist as processes until explicitly stopped or they crash.

Key concepts you'll see throughout this guide:

  • PID: Unique integer assigned by the kernel. Recycled after a process exits.
  • PPID: Parent PID. Every process was spawned by another process.
  • PID 1: The init system (systemd on modern distros). First process started by the kernel; parent of all others.
  • Daemon: A background process with no controlling terminal — sshd, nginx, cron.
  • Thread: A lightweight execution unit within a process, sharing the same memory space. The kernel schedules threads independently.
  • Zombie: A process that has exited but whose parent hasn't called wait() to collect its exit status. Shows as state Z. Harmless in small numbers; a large accumulation indicates a bug in the parent.

ps — Snapshot of Processes

ps prints a point-in-time snapshot of running processes. It reads from the /proc filesystem and exits — it's not live.

# Your own processes only (minimal output):
ps

# All processes, all users, with detailed info — the most common invocation:
ps aux

# a = all users' processes with a terminal
# u = user-oriented format (shows USER, %CPU, %MEM, RSS)
# x = include processes without a controlling terminal (daemons)

# Show full process tree with parent-child relationships:
ps auxf

# Sort by CPU (descending):
ps aux --sort=-%cpu | head -20

# Sort by resident memory (actual RAM in use):
ps aux --sort=-%mem | head -20

# Find a specific process:
ps aux | grep nginx

# Show all threads (useful for diagnosing multi-threaded apps):
ps -eLf | grep java

Reading ps Output

USER       PID  %CPU %MEM    VSZ    RSS TTY      STAT START   TIME COMMAND
root         1   0.0  0.1 169012  12348 ?        Ss   Jun01   0:03 /sbin/init
deploy    1234   4.2  2.1 512342  87643 ?        Sl   10:22   0:45 python3 app.py
www-data  8891   0.8  0.4 214532  18200 ?        S    11:05   0:02 nginx: worker
  • VSZ: Virtual memory size in KB — includes memory-mapped files, shared libraries, and swapped-out pages. Large VSZ is not alarming by itself.
  • RSS: Resident Set Size — physical RAM actually in use, in KB. This is the number to watch when diagnosing memory pressure.
  • STAT codes:
    • R — Running or runnable (on the CPU or in the run queue)
    • S — Interruptible sleep (waiting for an event, e.g., network data)
    • D — Uninterruptible sleep (waiting for I/O — cannot be killed)
    • Z — Zombie (exited, waiting for parent to reap)
    • T — Stopped (via SIGSTOP or Ctrl+Z)
    • s — Session leader
    • l — Multi-threaded
    • + — In the foreground process group

top and htop — Real-Time Monitoring

top ships on every Linux system and updates the process list every 3 seconds by default. htop adds color, mouse support, and a cleaner layout — install it if it's not already present.

# Install htop:
sudo apt install htop       # Ubuntu/Debian
sudo dnf install htop       # Fedora
sudo pacman -S htop         # Arch

# Launch either:
top
htop

The top header tells you a lot at a glance:

top - 14:22:01 up 5 days,  2:13,  1 user,  load average: 0.52, 0.48, 0.44
Tasks: 198 total,   1 running, 197 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.3 us,  0.8 sy,  0.0 ni, 96.5 id,  0.2 wa,  0.0 hi,  0.2 si
MiB Mem :  7934.3 total,  1203.5 free,  4821.2 used,  1909.6 buff/cache
MiB Swap:  2048.0 total,  2048.0 free,     0.0 used.  2810.3 avail Mem
  • load average: Average number of processes waiting for CPU over 1, 5, and 15 minutes. On a 4-core system, sustained values above 4.0 indicate saturation.
  • wa (iowait): CPU time spent waiting for I/O to complete. Consistently above 5–10% suggests a disk or NFS bottleneck.
  • buff/cache: Memory the kernel has claimed for disk cache. It is released immediately when applications request RAM — do not count this as "used" when assessing memory pressure.
  • avail Mem: More accurate than "free" — includes reclaimable cache.

Useful interactive keys in top: P sort by CPU, M sort by memory, k kill a PID, r renice, 1 expand individual CPU cores, u filter by user, q quit. In htop: F5 tree view, F9 signal menu, F3 search, u filter by user.

Signals and kill

Signals are asynchronous notifications sent to processes. kill is the tool to send them — the name is misleading, it sends any signal, not just termination signals.

# List all available signals:
kill -l

# SIGTERM (15) — ask the process to exit gracefully; it can catch this and clean up:
kill 1234
kill -SIGTERM 1234

# SIGKILL (9) — kernel-enforced immediate termination; process cannot intercept this:
kill -9 1234

# SIGHUP (1) — historically "terminal hangup"; daemons use it as a convention to reload config:
kill -HUP 1234

# SIGSTOP — pause a process (cannot be caught or ignored):
kill -STOP 1234

# SIGCONT — resume a stopped process:
kill -CONT 1234

# Kill all processes matching a name:
killall nginx
pkill nginx

# Send signal to processes matching a pattern:
pkill -HUP nginx        # reload all nginx workers
pkill -u username       # kill all processes owned by a user

# Find PIDs without killing:
pgrep nginx
pidof nginx

The correct workflow is always: try SIGTERM first, wait 5–10 seconds, then escalate to SIGKILL if the process hasn't exited. Jumping straight to kill -9 skips the process's cleanup handlers — open files may not be flushed, database transactions may be left incomplete, temp files may not be removed.

Signal Reference

SignalNumberCatchableUse Case
SIGTERM15YesNormal shutdown — always try first
SIGKILL9NoForce-kill an unresponsive process
SIGHUP1YesReload daemon config without restart
SIGINT2YesWhat Ctrl+C sends from the terminal
SIGSTOP19NoPause a process (Ctrl+Z sends SIGTSTP, which is catchable)
SIGCONT18YesResume a stopped process
SIGUSR1/210/12YesApplication-defined — check the app's documentation

Process Priority with nice and renice

The Linux scheduler assigns CPU time based on a process's nice value, ranging from -20 (highest priority — least nice to other processes) to +19 (lowest priority — most deferential). Normal processes start at 0. Only root can set negative nice values.

# Start a CPU-heavy build at low priority so it doesn't starve interactive work:
nice -n 15 make -j$(nproc)

# Start a process with elevated priority (requires root):
sudo nice -n -10 latency-sensitive-app

# Change the priority of a running process:
renice -n 10 -p 1234           # lower priority (more nice)
sudo renice -n -5 -p 1234      # higher priority (less nice, root required)

# Lower priority for all processes owned by a user:
sudo renice -n 10 -u build-user

# Verify — the NI column in ps or top shows the nice value:
ps -o pid,ni,comm -p 1234

The PR (priority) column in top shows 20 + NI — so a nice value of 0 shows as PR 20, and -5 shows as PR 15. Real-time processes show rt in that column.

Practical use cases: set large compilation jobs to nice 19 on shared build servers; set backup scripts to nice 15 so they don't impact production; use ionice (see below) alongside nice to control I/O scheduling as well.

# ionice: set I/O scheduling class (useful for backup/rsync jobs)
# Class 3 = idle — only gets disk access when nothing else needs it:
ionice -c 3 rsync -av /data /backup

# Run a job with both low CPU and low I/O priority:
nice -n 19 ionice -c 3 tar czf backup.tar.gz /var/data

Background and Foreground Jobs

# Start a command in the background immediately:
python3 myscript.py &

# Suspend a running foreground process (Ctrl+Z), then send it to background:
# [Press Ctrl+Z]
bg %1

# List current shell's background jobs:
jobs -l

# Bring a background job to the foreground:
fg %1

# Survive terminal close — nohup redirects stdout/stderr to nohup.out:
nohup python3 long_script.py &

# Detach an already-running background job from the shell:
python3 script.py &
disown %1

# For persistent background work, tmux or screen is cleaner:
tmux new -s mysession
# run your command, then Ctrl+B D to detach
tmux attach -t mysession

systemd Service Management

On modern Linux, long-running processes belong under systemd supervision. systemd handles automatic restarts, dependency ordering, log collection via journald, and resource limits via cgroups.

# Core service operations:
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx # reload config without full restart (if the unit supports it)

# Boot persistence:
sudo systemctl enable nginx # create symlink to start on boot
sudo systemctl disable nginx
sudo systemctl enable --now nginx # enable AND start in one step

# Inspect a service:
systemctl status nginx
journalctl -u nginx # all logs for this unit
journalctl -u nginx -f # follow live
journalctl -u nginx --since "1


Go up

This site uses cookies for analytics and advertising (Google AdSense). By continuing to browse, you accept our use of cookies. Learn more