Job Control: bg, fg, jobs, and nohup Explained

Job Control: bg, fg, jobs, and nohup Explained

Running one command at a time in one terminal window works fine until it doesn’t, until you need a long task to keep running while you do something else, or need it to survive after you disconnect entirely. Job control, and the related nohup command, are how Bash handles exactly that without needing multiple terminal windows or a tool like tmux.

Foreground and background

By default, a command you type runs in the foreground: it takes over your terminal, and you cannot type another command until it finishes (or you interrupt it).

sleep 300

This blocks the terminal for 300 seconds. To run something in the background instead, from the moment it starts, append &:

sleep 300 &
[1] 12345

The shell immediately returns control to you, printing a job number ([1]) and the process’s PID (12345). The command keeps running, but no longer occupies your terminal.

Suspending a running foreground job

Ctrl+Z suspends whatever is currently running in the foreground, pausing it entirely (it stops executing, unlike backgrounding it with &, which keeps it actively running) and handing control back to the shell:

sleep 300
# press Ctrl+Z
[1]+  Stopped                 sleep 300

At this point the job is paused, not running in the background yet. From here you choose what happens next.

Moving jobs between foreground and background

jobs          # list jobs in the current shell session
bg %1          # resume job 1, running in the background
fg %1          # bring job 1 to the foreground

bg resumes a suspended job so it continues running, but in the background rather than occupying the terminal. fg brings a background or suspended job back to the foreground, where it behaves like a normal command again, including being interruptible with Ctrl+C or suspendable again with Ctrl+Z.

jobs -l

-l includes the PID alongside the job number, useful since job numbers ([1], [2]) are only meaningful within the current shell session, while the PID is the process’s actual system-wide identifier.

Why background jobs still die when you close the terminal

A common surprise: starting something with & does not protect it from the terminal closing. When a terminal session ends, or an SSH connection drops, the shell sends SIGHUP to every job in that session, foreground or background, and any process that has not specifically chosen to ignore that signal terminates as a result.

long-running-task &
# closing this terminal will still kill long-running-task

Surviving disconnection with nohup

nohup runs a command in a way that makes it ignore SIGHUP specifically:

nohup long-running-task &

Combining nohup with & does two separate things: nohup makes the process immune to the terminal-closing signal, and & starts it in the background so the shell is immediately free. By default, nohup redirects the command’s output to a file named nohup.out in the current directory, since there is no longer a terminal to print to once you disconnect:

nohup ./backup-script.sh > backup.log 2>&1 &

Redirecting explicitly, as shown here, is generally clearer than relying on the default nohup.out, since it lets you choose exactly where the output goes.

disown: protecting a job you already started

If you forgot to use nohup when starting a command, disown achieves a similar result after the fact, by removing the job from the shell’s job table so the shell no longer sends it SIGHUP on exit:

long-running-task &
disown %1

disown works on a job that is already running in the current shell; nohup needs to be specified at the moment the command starts. Both solve the same underlying problem, but apply at different points in a command’s lifecycle.

Job control in practice

A realistic sequence combining several of these: start a long task, realize you need to step away, and want it to survive:

./long-task.sh
# realize you need this to survive disconnecting; press Ctrl+Z to suspend it
bg              # resume it running in the background
disown           # detach it from this shell's job table
# now safe to close the terminal or disconnect

Alternatively, running long tasks inside tmux or screen sidesteps this entirely, since the session itself, not any individual terminal window, is what stays alive, and nohup/disown become unnecessary for anything started inside it.

Frequently Asked Questions

What is the difference between running a command with & and pressing Ctrl+Z?

Appending & to a command starts it directly in the background from the moment it runs, and your shell prompt returns immediately without ever waiting on it in the foreground. Ctrl+Z instead suspends a command that is already running in the foreground, pausing it entirely (it stops executing, it does not keep running in the background automatically) and returning control to the shell; from there you would use bg to actually resume it running in the background, or fg to bring it back to the foreground. In short, & starts something in the background from the start, while Ctrl+Z pauses something already running so you can then choose where it continues.

What happens to a background job if I close the terminal?

By default, closing the terminal (or losing an SSH connection) sends SIGHUP to every job started in that session, including background jobs, which terminates them unless the specific program has chosen to ignore or handle that signal itself. This is true regardless of whether a job is running in the foreground or background, background jobs are not automatically protected from the terminal closing. To keep a process running after the terminal closes, start it with nohup, or run it inside a terminal multiplexer like tmux or screen, which keeps the session itself alive independently of any one terminal window.

What does nohup actually do?

nohup runs a command in a way that makes it ignore the SIGHUP signal, the signal normally sent to a process when its controlling terminal disconnects. Without nohup, closing the terminal or losing an SSH connection typically terminates any process still tied to that session. nohup command & combines both: the command becomes immune to the terminal-closing signal, and & starts it in the background so your shell is immediately free to do other things. By default nohup redirects the command’s output to a file called nohup.out in the current directory, since there is no longer a terminal to print output to once you disconnect.

How do I bring a background job back to the foreground?

Use fg, which brings the most recently backgrounded or suspended job to the foreground. If multiple jobs are running, jobs lists them with numbers, and fg %2 brings job number 2 specifically to the foreground (the percent sign is required syntax for referring to a job by its job number). Once a job is in the foreground, it behaves like any normal foreground command: it can be suspended again with Ctrl+Z, or interrupted entirely with Ctrl+C.

How do I see what background jobs are currently running?

Run jobs, which lists every job associated with the current shell session, along with its job number, state (running, stopped), and the command itself. Job numbers shown by jobs (like [1], [2]) are specific to the current shell session and are not the same as the process’s system-wide PID; to get the PID, use jobs -l, which includes it alongside the job number.

What is the difference between nohup and disown?

nohup is used when starting a command, and makes that command immune to SIGHUP from the very beginning: nohup mycommand &. disown is used on a job that is already running in the current shell, removing it from the shell’s job table so that the shell no longer sends it SIGHUP when the shell itself exits, without needing to have started it with nohup in the first place: mycommand & followed later by disown %1. Both achieve a similar practical result, a process surviving after the terminal closes, but disown is useful specifically when you forgot to use nohup at the time you started the command.