Skip to main content

Processes and Jobs

Every program running on a Linux system is a process. Understanding how to view what is running, how to control processes, and how to manage background services is essential for maintaining a production server. This chapter covers process inspection, signals, background jobs, and the systemd service manager.


Process Basics

When you run a command, the shell creates a new process. Each process has:

  • A PID (Process ID) — unique integer identifier
  • A PPID (Parent Process ID) — the PID of the process that created it
  • An owner (the user who started it)
  • CPU and memory usage statistics
  • A state (running, sleeping, zombie, stopped)

The init or systemd process always has PID 1 and is the ancestor of all other processes.


Viewing Processes — ps

ps (process status) shows a snapshot of current processes.

# Processes in the current shell session
ps

# All processes from all users, in BSD syntax (most common usage)
ps aux

# All processes, full format (includes PPID, start time)
ps -ef

# Sort by CPU usage
ps aux --sort=-%cpu | head 20

# Sort by memory usage
ps aux --sort=-%mem | head 20

# Show a specific process by name
ps aux | grep nginx

# Show process tree (parent-child relationships)
ps axjf

# Show threads
ps axms

Understanding ps aux Output

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ubuntu 1234 0.0 0.1 14500 2048 pts/0 Ss 09:00 0:00 bash
ubuntu 5678 2.4 1.2 623456 24576 pts/0 Sl 09:15 0:42 java -jar app.jar
root 9012 0.0 0.0 5756 812 ? Ss Apr09 0:00 /usr/sbin/sshd
ColumnMeaning
USERProcess owner
PIDProcess ID
%CPUCPU usage percentage
%MEMMemory usage percentage
VSZVirtual memory size (KB)
RSSResident Set Size — actual RAM used (KB)
TTYTerminal (? means no terminal)
STATProcess state (S=sleeping, R=running, Z=zombie, D=uninterruptible sleep)
STARTStart time
TIMETotal CPU time consumed
COMMANDCommand and arguments

Interactive Process Monitor — top and htop

top

top shows a continuously updated view of system resource usage.

top

Key bindings inside top:

KeyAction
qQuit
kKill a process (prompts for PID and signal)
MSort by memory
PSort by CPU (default)
uFilter by user
1Show individual CPU cores
dChange update interval
hHelp

htop

htop is an improved, colour-coded top with mouse support. Install it first:

sudo apt install htop # Ubuntu/Debian
sudo dnf install htop # RHEL/Fedora
htop

# Filter by process name
htop -p $(pgrep java | tr '\n' ',')

htop shows a visual CPU/memory bar at the top and allows you to scroll, search (/), and kill processes with the function keys.


Signals and kill

Signals are software interrupts sent to processes. Every process can define how it handles each signal (except SIGKILL and SIGSTOP, which cannot be caught or ignored).

Common Signals

SignalNumberDefault ActionWhen to Use
SIGTERM15Graceful terminationAsk a process to stop cleanly (default for kill)
SIGKILL9Immediate killForce-kill a process that won't stop
SIGHUP1Terminate (or reload config)Reload config without restart (e.g., nginx, sshd)
SIGINT2InterruptSame as pressing Ctrl+C
SIGSTOP19PausePause a process
SIGCONT18ContinueResume a paused process
SIGUSR110User-definedApplication-specific (e.g., log rotation trigger)

Sending Signals

# Send SIGTERM (graceful stop) — default
kill 1234

# Send SIGKILL (force kill)
kill -9 1234
kill -SIGKILL 1234

# Reload config (SIGHUP)
kill -HUP $(cat /var/run/nginx.pid)

# Kill by name (kills all processes matching the name)
killall nginx

# Kill all processes matching a pattern
pkill -f "java -jar myapp"

# Graceful killall
killall -s SIGTERM node

# Send signal to all processes of a user
pkill -u ubuntu

Finding PIDs

# Get PID of a named process
pgrep nginx
# 1234
# 1235

# Get PID and process name
pgrep -a nginx

# More precise matching
pgrep -x nginx # exact name match
pgrep -f "java.*myapp.jar" # match against full command line

# Combine with kill
kill $(pgrep nginx)

# Or use pkill directly
pkill nginx

Background Jobs

Running in the Background — &

Append & to run a command in the background, immediately returning your shell prompt.

# Run in background
./long-running-script.sh &
# [1] 5678 (job number and PID)

# The script continues running; you get your prompt back

Listing Jobs — jobs

jobs
# [1]+ Running ./long-running-script.sh &
# [2]- Stopped vim file.txt

Foreground and Background — fg / bg

# Bring job 1 to the foreground
fg %1

# Send a foreground process to the background:
# 1. Press Ctrl+Z to suspend it
# 2. Then run:
bg %1 # resume it in the background

# Bring the most recent job to foreground
fg

Surviving Disconnects — nohup

When you disconnect from SSH, all processes you started (including background jobs) receive SIGHUP and terminate. nohup prevents this.

# Run a command that survives disconnection
nohup ./deploy.sh &

# Output goes to nohup.out by default
nohup ./deploy.sh > /var/log/deploy.log 2>&1 &

# Get the PID
echo $!
# 5679

screen and tmux — Terminal Multiplexers

For serious long-running tasks, terminal multiplexers are better than nohup:

# tmux — create a new session
tmux new -s deploy

# Detach from session (leaves it running): Ctrl+B, then D
# Reattach later:
tmux attach -t deploy

# List sessions
tmux ls

Managing Services with systemctl

Modern Linux systems use systemd as their init system and service manager. systemctl is the command-line interface to systemd.

Service Lifecycle

# Start a service
sudo systemctl start nginx

# Stop a service
sudo systemctl stop nginx

# Restart a service (stop then start)
sudo systemctl restart nginx

# Reload config without full restart (if the service supports it)
sudo systemctl reload nginx

# Restart if running, start if stopped
sudo systemctl reload-or-restart nginx

# Check service status
sudo systemctl status nginx

# Enable a service to start on boot
sudo systemctl enable nginx

# Disable autostart on boot
sudo systemctl disable nginx

# Enable and start in one command
sudo systemctl enable --now nginx

# Check if a service is running
systemctl is-active nginx # prints "active" or "inactive"
systemctl is-enabled nginx # prints "enabled" or "disabled"

Status Output

systemctl status nginx
# ● nginx.service - A high performance web server and a reverse proxy server
# Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
# Active: active (running) since Fri 2026-04-10 09:00:00 UTC; 2h 15min ago
# Docs: man:nginx(8)
# Main PID: 1234 (nginx)
# Tasks: 3 (limit: 4660)
# Memory: 5.2M
# CPU: 142ms
# CGroup: /system.slice/nginx.service
# ├─1234 nginx: master process /usr/sbin/nginx -g daemon off;
# ├─1235 nginx: worker process
# └─1236 nginx: worker process
#
# Apr 10 09:00:00 myserver systemd[1]: Started A high performance web server...

Listing and Filtering Services

# List all active services
systemctl list-units --type=service

# List all services (including inactive)
systemctl list-units --type=service --all

# List services that failed
systemctl list-units --state=failed

# Show enabled/disabled state of all services
systemctl list-unit-files --type=service

Writing a systemd Service Unit

Create /etc/systemd/system/myapp.service:

[Unit]
Description=My Node.js Application
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/opt/myapp/current
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
# Reload systemd to pick up the new unit file
sudo systemctl daemon-reload

# Enable and start the service
sudo systemctl enable --now myapp

# Verify it started
sudo systemctl status myapp

Reading Logs with journalctl

journalctl queries the systemd journal — the centralized log storage for all services managed by systemd.

# All logs (newest at end)
journalctl

# Follow live log output (like tail -f)
journalctl -f

# Logs for a specific service
journalctl -u nginx

# Follow logs for a service
journalctl -u nginx -f

# Show logs from the current boot only
journalctl -b

# Show logs from the previous boot
journalctl -b -1

# Filter by priority (err, warning, info, debug)
journalctl -p err

# Show logs from the last hour
journalctl --since "1 hour ago"

# Show logs in a time range
journalctl --since "2026-04-10 09:00:00" --until "2026-04-10 10:00:00"

# Show last 100 lines
journalctl -n 100 -u myapp

# Output in JSON format (useful for log forwarding)
journalctl -u myapp -o json-pretty

# Filter by process ID
journalctl _PID=1234

# Disk usage of the journal
journalctl --disk-usage

Combining journalctl with grep

# Find errors in the nginx journal from the last hour
journalctl -u nginx --since "1 hour ago" | grep -i error

# Watch for authentication failures in real time
journalctl -f | grep -i "authentication failure"

Practical Scenarios

Check What Is Using a Port

# Which process is listening on port 8080?
sudo ss -tlnp | grep :8080
# or
sudo lsof -i :8080

Kill a Stuck Java Process Gracefully

# Find the PID
pgrep -f "java.*myapp.jar"
# 5678

# Try graceful stop first (SIGTERM)
kill 5678

# Wait a few seconds, check if it stopped
sleep 5
kill -0 5678 2>/dev/null && echo "still running" || echo "stopped"

# If still running, force kill
kill -9 5678

Restart a Service After Config Change

# Validate nginx config before restarting
sudo nginx -t

# If valid, reload gracefully (zero downtime)
sudo systemctl reload nginx

# Or full restart if reload is not supported
sudo systemctl restart nginx

Debug a Service That Won't Start

# Check the status with recent journal entries
sudo systemctl status myapp

# Show all journal entries for the service (most useful)
sudo journalctl -u myapp --no-pager

# Check if the binary is where the unit file expects it
which node
ls -la /opt/myapp/current/server.js

# Try running the ExecStart command manually as the service user
sudo -u deploy /usr/bin/node /opt/myapp/current/server.js