Jobs and backgrounding
Job control lets you suspend, background, and foreground processes within a shell session. nohup and disown protect long-running jobs from SIGHUP when the terminal closes — without them, backgrounding alone is not enough.
You start a long-running build, realize you need to do something else in the same terminal, and instinctively reach for Ctrl-C — killing the build entirely. Or you background a job with &, close the terminal, and come back to find the job dead. Both are avoidable. The shell has a full job-control subsystem: suspend, resume, background, foreground, and detach. Once you know those five operations, you stop wasting either your time or your compute.
After this lesson you can background a new command with &, suspend a running command with Ctrl-Z, inspect jobs with jobs, move jobs between foreground and background with fg and bg, and protect long-running jobs from terminal closure with nohup and disown.
& starts a command in the background immediately. Append & to any command and the shell forks the child into a background process group, prints the job number and PID, and returns your prompt immediately.
python train.py &
# [1] 2047[1] is the job number — a shell-local index. 2047 is the PID. The process is running; your prompt is available. Its stdout and stderr still go to the terminal unless you redirect them.
python train.py > train.log 2>&1 &Now output goes to train.log and you have a clean terminal. The background process is still in the same session as the shell — it will receive SIGHUP when the session ends (see Step 5).
Ctrl-Z sends SIGTSTP to the foreground process group, suspending it. The process is not killed — it is paused (STAT T). The shell regains control and prints:
^Z
[1]+ Stopped vim notes.txtThe job is now stopped. Its state in /proc/<PID>/status is T (traced/stopped). It holds all its memory, open files, and sockets — nothing is freed. You can resume it later.
Why this matters: Ctrl-Z is how you escape from an interactive program without losing your work. Suspend vim, run some shell commands, then bring vim back.
jobs lists all jobs in the current shell session.
jobs
# [1]- Stopped vim notes.txt
# [2]+ Running python train.py &The + marks the current job (most recently foregrounded or stopped). The - marks the previous job. You address jobs by number: %1, %2, or %% for the current job.
fg brings a job to the foreground. It reconnects stdin/stdout to the terminal and puts the process back in the foreground process group.
fg %1 # bring job 1 (vim) to foreground
fg # bring the current job (marked +) to foregroundbg resumes a stopped job in the background. It sends SIGCONT but does not move it to the foreground.
bg %1 # resume job 1 in the backgroundnohup makes a command immune to SIGHUP. When you log out or close a terminal, the shell sends SIGHUP to its entire process group. Any background job that has not been protected will die. nohup redirects stdin from /dev/null, appends stdout+stderr to nohup.out by default, and installs a SIGHUP ignore handler before exec-ing the command.
nohup python train.py > train.log 2>&1 &
echo $! # PID of the just-launched background jobThe process now survives terminal closure. nohup.out is only used if you don’t redirect yourself — always redirect explicitly to avoid litter.
disown removes a job from the shell’s job table after the fact. If you forgot to use nohup but the job is already running, disown severs the shell-job relationship. The shell will no longer send SIGHUP to that process when it exits.
./long-script.sh & # forgot nohup
jobs # [1]+ Running ./long-script.sh
disown %1 # remove from job table
jobs # (empty — shell no longer tracks it)The process is now orphaned from the shell’s perspective. Linux re-parents it to PID 1 (init), which never sends SIGHUP. The process runs until it finishes or is killed explicitly.
disown -h keeps the job in the job table but marks it to not receive SIGHUP — useful if you still want jobs to show it.
Run a long backup, check on it mid-way, then protect it from logout.
# Start backup in background with output captured
tar czf /backup/home.tar.gz /home/alice > /tmp/backup.log 2>&1 &
echo "PID: $!"
# Check progress without interrupting
jobs
tail -f /tmp/backup.log # Ctrl-C to exit tail, backup keeps running
# You realize you need to close this SSH session.
# Protect the job:
disown %%
# Verify it is detached
jobs # empty
ps aux | grep tar # still runningIf you had used nohup at launch instead:
nohup tar czf /backup/home.tar.gz /home/alice > /tmp/backup.log 2>&1 &Both approaches work; nohup at launch is simpler. disown saves you when you forgot.
▸Why this works
Job control (&, fg, bg, jobs) is a feature of interactive shells. In non-interactive scripts (run via cron, systemd, or CI pipelines), job control is disabled by default. Background & still works to fork a process, but fg and bg will fail with “no job control.” For long-running processes in those contexts, use process managers (systemd units, supervisor) rather than shell job control.
▸Common mistake
Backgrounding a job with & alone does NOT protect it from SIGHUP. This is the single most common job-control mistake. sleep 3600 & then closing the terminal kills the sleep. The background flag only means “don’t block the prompt” — it says nothing about SIGHUP immunity. Always pair & with either nohup (at launch) or disown (after the fact) when you need the job to survive terminal closure.
You run: python train.py & then close the terminal. The training process dies. What should you have done?
& starts a command in the background; Ctrl-Z suspends the foreground process (SIGTSTP, STAT T). jobs lists jobs by number; fg %n brings a job to the foreground; bg %n resumes a stopped job in the background (SIGCONT). Background jobs still receive SIGHUP when the terminal closes — & alone does not protect them. nohup ignores SIGHUP at launch and redirects output to nohup.out; disown removes an already-running job from the shell’s job table, severing the SIGHUP link.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.