I remember the first time I accidentally started a long-running compile job in the foreground of an SSH session and realized I couldn’t get my prompt back without killing the whole thing. That’s when I actually learned what bg does, and it changed how I use the terminal every single day since. It’s a small command, but it’s tied to one of the more misunderstood parts of the shell: job control.
Let me walk through everything about bg — how it works, its relationship to fg, jobs, and signals, and how the shell’s job control subsystem operates under the hood.
What bg Does
bg resumes a stopped job and continues running it in the background, letting you get your terminal prompt back while the job keeps executing. It’s almost always used alongside three other job-control features: suspending a foreground job with Ctrl+Z, listing jobs with jobs, and bringing something back to the foreground with fg.
Basic Syntax
bg [job_spec ...]
If you don’t give it a job spec, bg acts on the “current” job — the one marked with a + in jobs output. You can also target a specific job using:
%1,%2, etc. — job number%string— job whose command starts withstring%?string— job whose command containsstringanywhere%%or%+— the current job%-— the previous job
A Practical Walkthrough
Here’s a session I ran to demonstrate the full lifecycle, with job control explicitly enabled (set -m, which is on by default in interactive shells):
$ set -m
$ sleep 60 &
$ jobs -l
[1]+ 694 Running sleep 60 &
Here I started sleep 60 directly in the background using &. But the more common scenario is starting something in the foreground and suspending it:
$ sleep 60
^Z
[1]+ Stopped sleep 60
Pressing Ctrl+Z sends SIGTSTP to the foreground process group, pausing it and returning control to the shell. At this point the job is stopped, not running — it’s frozen in place, consuming no CPU, but still resident in memory.
Now bg resumes it in the background:
$ bg %1
[1]+ sleep 60 &
$ jobs -l
[1]+ 698 Running sleep 60 &
I tested this exact sequence and confirmed the state transitions:
$ kill -STOP %1
$ jobs -l
[1]+ 698 Stopped (signal) sleep 60
$ bg %1
$ jobs -l
[1]+ 698 Running sleep 60 &
So bg is really just sending SIGCONT to a job that’s currently stopped, combined with telling the shell to stop treating it as the foreground job.
Why bg Needs Job Control Enabled
One thing that trips people up — including me the first time I tried to script this — is that bg only works in shells with job control active. Non-interactive shells (like a script running via bash script.sh or executed through most automation tools) have job control disabled by default:
$ bash -c '
sleep 50 &
bg %1
'
bash: line 3: bg: no job control
To force job control on inside a script, you have to explicitly enable it:
$ bash -c '
set -m
sleep 60 &
bg %1
'
bash: line 4: bg: job 1 already in background
Note that in this second example, bg complained the job was “already in background” because I started it with & in the first place — bg is only meaningful for jobs that are currently stopped. If a job is already running in the background, there’s nothing for bg to do.
Parameters and Related Options
bg itself takes almost no options beyond the job specifiers — it’s intentionally minimal. The real “parameters” worth understanding are the job specs and the related built-ins that make up the job control system:
| Command | Purpose |
|---|---|
command & | Start a new job directly in the background |
Ctrl+Z | Suspend the current foreground job (sends SIGTSTP) |
jobs | List jobs and their states |
jobs -l | List jobs with PIDs included |
bg %N | Resume stopped job N in the background |
fg %N | Bring job N to the foreground |
wait %N | Block until job N completes |
disown %N | Remove a job from the shell’s job table without stopping it |
How Job Control Works Internally
This is the part I find genuinely interesting. Job control in Linux (and Unix generally) is built around process groups and sessions:
- Every process belongs to a process group, and every process group belongs to a session.
- A terminal (or pseudo-terminal, in the case of SSH) has one process group designated as the foreground process group. Only that group is allowed to read from the terminal and receive terminal-generated signals like SIGINT (Ctrl+C) and SIGTSTP (Ctrl+Z).
- When you run
command &, the shell creates a new process group for that command and does not make it the foreground group — so it runs independently, without hogging terminal input. - When you press Ctrl+Z on a foreground job, the terminal driver sends SIGTSTP to every process in the foreground process group, and the shell reclaims the terminal, becoming the foreground group again itself.
bgdoes two things: it sends SIGCONT to every process in that job’s process group (waking it back up), and it makes sure that process group is not set as the terminal’s foreground group, so it doesn’t compete for terminal input and won’t be interrupted by Ctrl+C from your keyboard.fgis the mirror image — it sends SIGCONT if needed, and explicitly sets that job’s process group as the foreground group so it can read from the terminal again.
This is why a job resumed with bg keeps running even if you then run other commands or even log out in some cases — though logging out (closing the controlling terminal) normally sends SIGHUP unless the job was started with nohup or disown -h.
Real-World Use Cases
Reclaiming a terminal after starting something heavy in the foreground. This is the classic case — I start a backup, realize it’ll take 20 minutes, hit Ctrl+Z, then bg to keep it running while I do something else in the same session.
Running multiple long jobs from one SSH session without opening new connections.
$ tar -czf backup1.tar.gz /data/set1
^Z
[1]+ Stopped tar -czf backup1.tar.gz /data/set1
$ bg
[1]+ tar -czf backup1.tar.gz /data/set1 &
$ tar -czf backup2.tar.gz /data/set2
^Z
[2]+ Stopped tar -czf backup2.tar.gz /data/set2
$ bg
[2]+ tar -czf backup2.tar.gz /data/set2 &
$ jobs
[1]- Running tar -czf backup1.tar.gz /data/set1 &
[2]+ Running tar -czf backup2.tar.gz /data/set2 &
Now both archive jobs run concurrently while I keep my shell.
Combining bg with wait in scripts for simple parallelism:
#!/bin/bash
for host in web1 web2 web3; do
ping -c 3 "$host" > "/tmp/$host.log" &
done
wait
echo "All pings completed"
This isn’t bg directly (since these start backgrounded already), but it’s the same underlying job-control machinery, and understanding bg/fg/jobs makes patterns like this much easier to reason about.
bg vs nohup vs disown vs &
People often mix these up, so here’s how I differentiate them:
&just starts a job in the background from the outset.bgresumes an already stopped job in the background — it doesn’t start anything new.nohup command &starts a background job that ignores SIGHUP, so it survives you closing the terminal.disown %1removes a job from the shell’s job table so it stops being tracked (and won’t receive SIGHUP when the shell exits), without affecting whether the process itself is running.
For anything that truly needs to survive a logout, I reach for nohup, setsid, or better yet a proper background service manager like systemd, screen, or tmux — bg alone doesn’t protect a job from hangup signals when the terminal session closes.
Troubleshooting Common Problems
“bg: no job control” in a script — add set -m at the top, or better, just restructure the script to use & directly and wait, since scripts rarely benefit from real interactive job control.
“bg: job not found” — the job number changes as jobs complete; run jobs again to get current numbers, since %1 might now refer to a different job than it did a minute ago.
“bg: current: no such job” or “already in background” — you’re calling bg on a job that’s not actually stopped. Check jobs -l state first.
Job dies right after you log out — this is expected default behavior; the shell sends SIGHUP to jobs when it exits, and unless a process specifically ignores it (or you used nohup/disown), it’ll terminate. This is not a bug in bg; it’s separate hangup behavior from the terminal/session layer.
Performance and Security Considerations
bg itself has no measurable performance overhead — it’s just a signal send and a bookkeeping update inside the shell. The considerations that matter are architectural: background jobs left running via bg still consume CPU, memory, and I/O just like any other process, and they’re still bound to your login session’s resource limits (ulimit) unless you’ve adjusted those. From a security angle, background jobs run with the same privileges as your shell, so there’s no privilege escalation risk introduced by bg itself — but leaving long-running jobs backgrounded in shared systems can surprise other admins who don’t expect resource-heavy processes tied to a dangling session.
Compatibility Across Distributions and Shells
bg, fg, and jobs are POSIX shell built-ins, and they work essentially the same in bash, zsh, and ksh across every major distribution — Ubuntu, Debian, RHEL, Fedora, Arch, and beyond. The one shell where this differs meaningfully is dash (Debian’s default /bin/sh), which does not support job control at all in non-interactive mode, which is exactly the “no job control” error I demonstrated above. If your scripts are run with sh rather than bash, don’t rely on bg/fg — use & and wait instead, since those work everywhere.
Combining bg With Terminal Multiplexers
In practice, I rarely rely on raw bg/fg alone for anything that needs to survive long-term — instead, I pair the concept with tmux or screen, which give you persistent sessions that keep running even after you disconnect entirely. Inside a tmux session, bg still works exactly the same way at the shell level, but the session itself survives your SSH connection dropping, which solves the hangup problem that plain bg doesn’t address on its own.
A typical workflow I use on a remote server:
tmux new -s build
$ make -j8
^Z
[1]+ Stopped make -j8
$ bg
$ exit_tmux_detach # Ctrl+b, d
Later, from anywhere:
tmux attach -t build
The build kept running in the background the whole time, and reattaching drops me right back into the same shell where I can fg it if I want to watch the remaining output.
Inspecting Job State in More Detail
Beyond the basic jobs and jobs -l, there are a couple of variants worth knowing:
jobs -p # print only PIDs, useful for scripting
jobs -r # list only running jobs
jobs -s # list only stopped jobs
I use jobs -p combined with kill when I want to terminate every backgrounded job in a session at once:
kill $(jobs -p)
This is a quick way to clean up a shell full of test processes I started for debugging without hunting down individual PIDs manually.
The Relationship Between bg and Shell Exit Behavior
By default, bash sends SIGHUP to background jobs when an interactive login shell exits, which is exactly why jobs resumed with bg don’t automatically survive you closing your terminal. Whether this happens depends on the huponexit shell option:
shopt huponexit
If you want backgrounded jobs to keep running after shell exit without wrapping every command in nohup, you can disable this behavior:
shopt -u huponexit
I don’t recommend this as a default habit though — it’s easy to forget about orphaned background jobs that no longer have any shell tracking them, which makes cleanup harder later. I’d rather rely on nohup, setsid, or a proper session multiplexer for anything genuinely meant to run unattended.
A Note on POSIX sh Compatibility
It’s worth remembering that bg is a feature of interactive job-control-capable shells, and it is explicitly listed as an optional POSIX utility that a conforming shell may or may not implement in non-interactive contexts. This is precisely why portable shebang scripts (#!/bin/sh) should never assume bg is available — always test job control behavior against the actual shell your script will run under, particularly if your deployment targets Alpine Linux or other systems that default to a lightweight, non-bash /bin/sh.
Summary
bg is a small command sitting on top of a genuinely elegant piece of Unix design: process groups, sessions, and terminal-driven signals. Once you understand that bg is really just “send SIGCONT and stop being the terminal’s foreground group,” a lot of related behavior — why Ctrl+Z works, why background jobs survive Ctrl+C, why closing your terminal can kill them — stops feeling like magic and starts feeling like a system you can reason about and control deliberately.
References
POSIX Shell and Utilities specification: https://pubs.opengroup.org/onlinepubs/9699919799/
Bash Reference Manual, Job Control: https://www.gnu.org/software/bash/manual/bash.html#Job-Control
man bash (search for JOB CONTROL section)
Linux signal(7) manual page: man 7 signal
