Foreground and Background Jobs
Every command you run in a terminal is either in the foreground, meaning it has control of the terminal and you have to wait for it to finish before typing anything else, or in the background, meaning it runs independently while you keep using the shell. Bash’s job control lets you start commands in the background, pause a running program and resume it later, and switch tasks between foreground and background on demand — all without opening a second terminal window.
Overview: How Job Control Works
When Bash starts a command, it creates a new process (or a group of processes, if the command is a pipeline) and assigns it to a process group called a job. Every interactive Bash shell tracks the jobs it has launched and gives each one a small integer job number (%1, %2, and so on) that is separate from the operating system’s process ID (PID). The job number only makes sense to that one shell session; the PID is the number the kernel actually uses to track the process.
A terminal (technically a pseudo-terminal, or pty, in most modern setups) has exactly one foreground process group at a time. Only the foreground process group is allowed to read keystrokes from the terminal and receive terminal-generated signals like Ctrl+C. Everything else attached to that terminal — every background job — keeps running, but the kernel will pause it automatically if it tries to read from the terminal (it receives SIGTTIN) and, depending on shell settings, may also pause it if it tries to write to the terminal.
You control which process group owns the foreground with three signals that Bash sends on your behalf:
- SIGTSTP (20) — sent when you press
Ctrl+Z. UnlikeSIGKILL, a process can catch and handle this, but by default it just suspends the process in place, freezing it exactly where it was. - SIGCONT (18) — sent by
fgorbgto wake a suspended process back up. - SIGINT (2) — sent when you press
Ctrl+C. This asks the foreground process to terminate; most programs exit, but some catch it and clean up first.
Backgrounding a command with the trailing & operator never suspends it — it starts the command already running, just not attached to your keyboard. Suspending happens only through Ctrl+Z on something already in the foreground.
There is one more piece of internals worth knowing: when your login shell exits (for example, you close the terminal window or log out over SSH), it normally sends SIGHUP (“hangup”) to every job still attached to it, killing them. This is exactly why long-running background jobs need special handling if you want them to survive after you disconnect — covered in the third example below.
Syntax
Job control is a handful of small, composable pieces rather than one command:
command & # start 'command' directly in the background
Ctrl+Z # suspend the current foreground job
jobs [-l] # list jobs known to this shell (-l also shows PIDs)
fg [%n] # bring job n (or the most recent job) to the foreground
bg [%n] # resume job n (or the most recent job) in the background
kill %n # send SIGTERM to job n
disown [%n] # remove job n from the shell's job table without killing it
nohup command & # start 'command' immune to SIGHUP, in the background
wait [%n] # block until job n (or all background jobs) finishes
| Symbol / Command | Meaning |
|---|---|
& |
Trailing operator that starts a command already running in the background. |
%1, %2, … |
Job specifiers, referring to jobs by the number jobs assigned them, not their PID. |
%+ or %% |
The “current” job — the most recently backgrounded or suspended one. |
%- |
The “previous” job. |
%name |
The job whose command line starts with name, e.g. %nano. |
Examples
Example 1: Starting a job in the background and bringing it back
$ sleep 300 &
[1] 48213
Bash immediately prints the job number in brackets (1) and the PID of the new process (48213), then hands the prompt straight back to you — it did not wait for sleep to finish. Check on it at any time:
$ jobs
[1]+ Running sleep 300 &
The + marks it as the current job. If you decide you actually want to wait for it after all, pull it into the foreground:
$ fg %1
sleep 300
Bash echoes the command and now your prompt is blocked until sleep finishes (or you press Ctrl+C to cancel it).
Example 2: Suspending a foreground program and resuming it in the background
Say you start editing a file and remember you need to run a quick command first:
$ nano deploy_notes.txt
While nano is running in the foreground, press Ctrl+Z. Bash prints:
[1]+ Stopped nano deploy_notes.txt
nano is now frozen in place, not destroyed — every unsaved change is still in memory. You get your prompt back and can run other commands. When you are ready to keep working in it, either resume it in the background:
$ bg %1
[1]+ nano deploy_notes.txt &
or bring it straight back to the foreground with fg %1, which is what you actually want for an interactive editor, since a backgrounded nano will just stop again the instant it tries to read your keystrokes (SIGTTIN). This example is really demonstrating the mechanism — bg is far more useful for commands that do real work without needing terminal input, which is the next example.
Example 3: A background job that survives logout
Backgrounding with & keeps a job running only as long as the shell that launched it stays alive. For a backup script you kick off over SSH and want to keep running after you disconnect, combine nohup (which blocks SIGHUP) with &:
#!/usr/bin/env bash
set -euo pipefail
SRC_DIR="/var/www/app"
DEST="/backups/app-$(date +%F).tar.gz"
tar -czf "$DEST" "$SRC_DIR"
echo "Backup written to $DEST"
$ chmod +x backup.sh
$ nohup ./backup.sh > backup.log 2>&1 &
[1] 51902
We redirected both stdout and stderr to backup.log ourselves, so nohup has nothing left to redirect and stays quiet. The job now ignores SIGHUP: closing the terminal or logging out will not kill it. If you’d rather just detach an already-running job from the current shell instead of restarting it with nohup, use disown:
$ disown %1
disown removes the job from this shell’s job table (so jobs no longer lists it and it won’t receive the shell’s SIGHUP), but unlike nohup it does not touch the process’s own signal handling — it only changes who “owns” it from the shell’s point of view.
How It Works Step by Step
-
Step-by-step is expressed as ul below for HTML compliance
- You type a command and press Enter. Bash forks a child process (or several, for a pipeline) and puts it in a new process group.
- If the command line ends in
&, Bash records that process group as a background job, prints its job number and PID, and immediately returns you to the prompt — it never makes that group the foreground process group of the terminal. - If there is no trailing
&, the new process group becomes the terminal’s foreground process group, and Bash’s own process waits (via thewait()system call family) for it to finish or stop before showing another prompt. - Pressing
Ctrl+ZsendsSIGTSTPto the foreground process group. The kernel suspends those processes, control returns to Bash, and Bash records the job as “Stopped”. bg %nsendsSIGCONTto that process group so it starts running again, but leaves it as a background job attached to no terminal input.fg %nalso sendsSIGCONT, but first makes that process group the foreground process group again, so it can read your keystrokes and receiveCtrl+C.- When the shell itself is about to exit, it sends
SIGHUPto every job still in its table (unless the job was started withnohupor laterdisown-ed), which by default terminates them.
Common Mistakes
Mistake 1: Closing the terminal and losing a long job
Starting a long task in the background and then closing the terminal window assumes it keeps running — it usually doesn’t:
$ ./generate_report.sh &
# closes terminal window
# report generation is killed by SIGHUP partway through
Fix it by making the job immune to SIGHUP before you disconnect, or run it inside tmux/screen so the whole session, not just one job, survives:
$ nohup ./generate_report.sh > report.log 2>&1 &
$ disown
Mistake 2: Confusing the job number with the PID
kill %1 and kill 1 are completely different things — the first politely asks your shell’s job 1 to terminate, the second tries to kill PID 1, which is the system’s init process, and will be refused unless you are root (and would be catastrophic if it succeeded). Always use % when you mean a job number:
$ kill 1 # WRONG if you meant job 1 — this targets PID 1
$ kill %1 # correct: sends SIGTERM to job 1
Mistake 3: Backgrounding a program that still needs your input
Interactive programs like editors, ssh without a command, or anything with a password prompt will stop themselves the instant they try to read the terminal, even after you background them:
$ mysql -u admin -p &
[1]+ Stopped (tty input) mysql -u admin -p
It looks backgrounded but is actually frozen waiting for input it can no longer receive. Run interactive programs in the foreground, or supply all their input up front (flags, a config file, input redirection) if they truly need to run unattended.
Best Practices
- Use
jobs -lwhen you need the PID for a job, instead of guessing it frompsoutput. - Prefer
%nameor%+over remembering raw numbers when you only have one or two jobs running. - Wrap anything that must outlive your login session in
nohup ... &, or better, run it insidetmuxorscreenso you can also reattach and watch its output later. - Redirect a background job’s output explicitly (
> out.log 2>&1) so it doesn’t try to write to a terminal that may no longer exist, and so you have a record of what happened. - Use
waitin scripts that launch several background tasks and need to know when they’ve all finished before continuing. - Never background an interactive program that will ask for input; run it in the foreground, or automate its input first.
- Check
$?right after a foregrounded job finishes if you need its exit status — running any other command first will overwrite it.
Practice Exercises
- Start
sleep 120 &, confirm it withjobs -l, then usefgto bring it to the foreground and pressCtrl+Cto cancel it before it finishes. Check$?afterward. - Open a file in
nano, suspend it withCtrl+Z, runlsandjobsto confirm it’s stopped, then usefgto return to editing (backgrounding an editor will just stop it again, as shown above). - Write a two-line script that sleeps for 10 seconds and then prints “done”, run it with
nohup ./script.sh & disown, and useps -p <PID>a few seconds later to confirm it’s still running and no longer tied to your shell’s job table.
Summary
- A trailing
&starts a command in the background immediately;Ctrl+Zsuspends a command that is already running in the foreground. jobslists the current shell’s jobs by job number (%1,%2, …), which is not the same as the process’s PID.fgandbgresume a job in the foreground or background respectively, both by sending itSIGCONT.- Only the foreground process group can read from the terminal; a backgrounded program that tries to read input will stop itself.
- Closing your terminal sends
SIGHUPto your jobs by default; usenohupordisown, or a terminal multiplexer liketmux, to let a job outlive the session. - Always use
%nwhen referring to a job number in commands likekill— a bare number is treated as a PID.
