Job control is how a single Bash session juggles more than one running command. Press Ctrl-Z to suspend the foreground job, type bg to let it keep running in the background, fg to pull it back to the foreground, and jobs to list everything the current shell owns. Each job has a number you target with a % job spec: fg %1, kill %2, bg %+. None of that survives logout though, because a backgrounded job is still a child of the shell and dies with it. To keep a process alive past the terminal closing, start it with nohup, or disown it after the fact, or detach it from the session entirely with setsid. Below is the full reference: suspend and resume, the job spec syntax, listing and signalling, and the three ways to outlive the shell.
How does job control work in Bash?
Job control is the Bash feature that lets one interactive shell start, suspend, resume, and signal multiple commands, each tracked as a job. Start a command with a trailing & to launch it in the background, or press Ctrl-Z to suspend the command currently running in the foreground. bg resumes a suspended job in the background; fg brings a background or suspended job to the foreground. jobs lists all jobs the current shell controls, each with a job number. You reference a job by its number with a percent sign: %1, %2, plus %+ (or %%) for the current job and %- for the previous one. kill %1 sends a signal to job 1. A backgrounded job is still a child of the shell, so it dies on logout unless you protect it with nohup, disown, or setsid. Job control is on by default in interactive shells; non-interactive scripts run with it off. For the loops and long-running commands you will most often background, see Bash While Loops.
Jump to:
- Suspend, background, and foreground
- Listing jobs with
jobs - Job specs:
%1,%+,%-,%string - Signalling jobs with
kill - Surviving logout:
nohup,disown,setsid - Which detach method to use
- Common pitfalls
- What to do next
- FAQ
Suspend, background, and foreground
The core loop is three keystrokes and two commands.
# Start a long job in the foreground
tar czf backup.tar.gz /var/www
# Press Ctrl-Z, Bash suspends it and hands you the prompt back
^Z
[1]+ Stopped tar czf backup.tar.gz /var/www
# Let it keep running in the background
bg
[1]+ tar czf backup.tar.gz /var/www &
# Pull it back to the foreground later
fg
tar czf backup.tar.gz /var/wwwWhat each step does:
Ctrl-ZsendsSIGTSTP, which stops (pauses) the foreground process and returns you to the prompt. The job is frozen, not running, until you resume it.bgresumes the most-recently-stopped job in the background. The command keeps running but you get your prompt back. It is the equivalent of having started it with&in the first place.fgmoves a job to the foreground and connects it to your terminal again, so its output goes to the screen andCtrl-Creaches it.
You can also background a command from the start with a trailing &:
sleep 300 &
[1] 48213Bash prints the job number in brackets ([1]) and the process ID (48213). The job number is the shell's local handle; the PID is the system-wide one.
Listing jobs with jobs
jobs shows every job the current shell owns:
jobs
[1] Running sleep 300 &
[2]- Running rsync -a src/ dest/ &
[3]+ Stopped vim notes.txtThe markers matter:
+is the current job, the default target when you typefgorbgwith no argument. Here that is job 3.-is the previous job, the second default. Here that is job 2.Runningmeans it is executing in the background;Stoppedmeans it is suspended (waiting forfgorbg).
Useful flags:
jobs -l # also show each job's PID
jobs -p # show PIDs only (handy for scripting: kill $(jobs -p))
jobs -r # only running jobs
jobs -s # only stopped jobsjobs -l is the one I reach for most, because it bridges the job number to the PID when I need to hand the PID to another tool.
Job specs: %1, %+, %-, %string
A job spec is how you name a job to fg, bg, kill, wait, and disown. It always starts with %:
| Job spec | Refers to |
|---|---|
%n | Job number n (e.g. %1, %2). |
%+ or %% | The current job (the one marked +). |
%- | The previous job (the one marked -). |
%string | The job whose command starts with string. |
%?string | The job whose command contains string. |
fg %2 # foreground job 2
bg %- # background the previous job
kill %1 # signal job 1
fg %vim # foreground the job whose command starts with "vim"
fg %?notes # foreground the job whose command contains "notes"A bare number is not a job spec. fg 2 returns fg: 2: no such job because Bash expects the leading %; get in the habit of writing fg %2. The %string forms are convenient interactively but fragile in scripts, where the job number is unambiguous.
Signalling jobs with kill
kill accepts job specs, not just PIDs, when job control is on:
kill %1 # send SIGTERM (15) to job 1, polite stop
kill -STOP %2 # suspend job 2 (same as Ctrl-Z would)
kill -CONT %2 # resume a stopped job
kill -9 %1 # SIGKILL, last resort, no cleanupkill -9 (SIGKILL) cannot be caught or ignored, so the process gets no chance to flush buffers or remove temp files. Reach for plain kill %1 (SIGTERM) first and only escalate to -9 if the process ignores it. To send a signal to every job at once, expand the PIDs: kill $(jobs -p).
wait blocks until a job finishes, which is the standard way to parallelise work and then collect it:
process_chunk a &
process_chunk b &
process_chunk c &
wait # block here until all three background jobs finish
echo "all chunks done"This pattern, fan out with &, then wait, is the simplest concurrency Bash offers. It works the same inside scripts and inside a Bash for or while loop that launches one background job per iteration.
Surviving logout: nohup, disown, setsid
A backgrounded job is still attached to your login session. When the terminal closes, the kernel sends SIGHUP ("hangup") to the session's processes, and most of them die. Three tools break that link.
nohup runs a command immune to SIGHUP, redirecting its output to nohup.out:
nohup ./long-import.sh &
# nohup: ignoring input and appending output to 'nohup.out'nohup only blocks the hangup signal; the trailing & is what backgrounds it. Redirect explicitly if you want the output somewhere other than nohup.out:
nohup ./long-import.sh > import.log 2>&1 &disown is the after-the-fact fix when you already started a job and only then realised you need to log out. It removes the job from the shell's job table so the shell will not send SIGHUP to it on exit:
./long-import.sh &
[1] 50912
disown -h %1 # mark job 1 to not receive SIGHUP, keep it in the table
# or
disown %1 # remove job 1 from the table entirelydisown -h keeps the job listed but shields it from the hangup; plain disown drops it from jobs output altogether. To detach in bulk, disown -a drops every job and disown -r drops only the running ones (leaving stopped jobs alone). Either way the process keeps running after logout. The catch: disown does not redirect output, so if the original terminal had stdout open to the screen, writes can fail once that terminal is gone. Redirect to a file before disowning if the process is chatty.
setsid starts the command in a brand-new session with no controlling terminal, so there is no session to hang up in the first place:
setsid ./long-import.sh > import.log 2>&1 < /dev/nullsetsid is the cleanest detach because the process is never a child of your shell's session. It is the closest thing to a one-line daemoniser. Redirect all three streams (stdin from /dev/null, stdout and stderr to a file) since the new session has no terminal to attach them to.
Which detach method to use
| Method | When you decide | Output handling | Best for |
|---|---|---|---|
nohup cmd & | Before starting | Auto-redirects to nohup.out | The default "start and walk away". |
disown %1 | After it is already running | None (redirect first) | "I forgot to nohup it." |
setsid cmd | Before starting | You redirect all three streams | A clean detach with no terminal at all. |
tmux / screen | Before starting | Full interactive session preserved | Anything you will want to reattach to and watch. |
For a process you genuinely need to revisit, reattach to, and interact with, none of these three beats a terminal multiplexer. nohup, disown, and setsid are fire-and-forget; tmux and screen give you the session back. Reach for the multiplexer when "keep it alive" really means "let me come back to it".
Common pitfalls
1. Expecting backgrounded jobs to survive logout on their own. A trailing & does not protect against SIGHUP. Use nohup, disown, or setsid if you are closing the terminal.
2. Forgetting the % in a job spec. kill 1 targets PID 1 (init/systemd, which the kernel will not let you kill anyway), while a bare number like kill 4823 would happily signal whatever unrelated process holds that PID. kill %1 targets job 1. The % is not optional.
3. disown without redirecting output. Once the controlling terminal closes, a disowned process writing to the old stdout can get SIGPIPE or fail. Redirect to a file before you disown anything chatty.
4. Job control is off in scripts. Job numbers and fg/bg are interactive-shell features. Inside a non-interactive script you still get & and wait, but % job specs and Ctrl-Z semantics behave differently. Use PIDs (pid=$!) in scripts.
5. Ctrl-Z stops, it does not background. Suspended is not the same as running. A job left Stopped is frozen and doing no work until you bg or fg it. Watch for Stopped in jobs output.
6. Trying to fg a job from a different shell. Jobs belong to the shell that started them. Open a new terminal and the job table is empty there; the process is still visible in ps, but not as a job.
7. Logout warning: "There are stopped jobs." Bash refuses to exit while jobs are Stopped, to stop you from accidentally killing paused work. Resume them, kill them, or type exit again to force it.
What to do next
- Bash While Loops: the long-running loops you will most often background with
&and collect withwait. - Run a Command as Another User on Linux: when the long job needs to run under a service account; combine
sudo -uwithnohuporsetsid. - External: the Bash Reference Manual: Job Control is the authoritative spec for job specs and the
fg/bg/jobsbuiltins. - External: the nohup(1) man page and setsid(1) man page document the detach tools exactly.
FAQ
See also
- ShellCheck: lint your bash scripts: catch the bugs in the scripts whose jobs you are managing.
- which vs type vs command -v: resolve which command a backgrounded job actually runs.
- How to run a command as another user: backgrounding work that runs as a service account.
- Bash while loops: the long-running loops you most often push to the background.
Sources
Authoritative references this article was fact-checked against.





