Job Control¶
Job control lets an interactive shell run commands in the background, stop them and move them between foreground and background. It also decides what happens to those commands when the terminal or SSH session closes, which is where most "my job died when I logged out" problems start.
Track: Core · Interview weight: Med
Must-Know Facts¶
| Fact | Value | Verify with |
|---|---|---|
cmd & | Runs in the background; the shell prints [job] PID | sleep 60 & |
jobs -l | Lists the shell's jobs with PIDs; + is current, - is previous | jobs -l |
Ctrl-Z | Sends SIGTSTP; the job stops | jobs shows Stopped |
bg %n / fg %n | Continue a job in the background / bring it to the foreground | bg %1 |
Ctrl-C | Sends SIGINT to the foreground process group; exit status 130 | echo $? |
Ctrl-D | End of input (EOF), not a signal; closes the shell at an empty prompt | cat then Ctrl-D |
| Job specs | %1, %+, %-, %sleep (name prefix) | kill %1 |
| Terminal hangup | The session leader gets SIGHUP; bash sends SIGHUP to all its jobs | close the terminal |
nohup | Ignores SIGHUP; writes output to nohup.out if stdout is a terminal | nohup cmd & |
disown | Removes a job from the shell's table, so bash does not send it SIGHUP | disown %1 |
setsid | Starts a command in a new session with no terminal | setsid cmd |
huponexit | If set, bash sends SIGHUP to jobs on a normal exit too (off by default) | shopt huponexit |
tmux / screen | Keep a whole terminal session alive on the server across disconnects | tmux attach |
Background, Stop and Resume¶
The session below ran in an interactive bash on a pseudo-terminal; ^Z and ^C are the keys typed.
sleep 300 &
sleep 400 &
jobs -l
sleep 500
# Ctrl-Z
jobs
bg %3
jobs
kill %1
jobs
fg %2
# Ctrl-C
echo $?
jobs
disown %3
jobs; ps -o pid,ppid,stat,comm -C sleep
Output:
$ sleep 300 &
[1] 2597
$ sleep 400 &
[2] 2598
$ jobs -l
[1]- 2597 Running sleep 300 &
[2]+ 2598 Running sleep 400 &
$ sleep 500
^Z
[3]+ Stopped sleep 500
$ jobs
[1] Running sleep 300 &
[2]- Running sleep 400 &
[3]+ Stopped sleep 500
$ bg %3
[3]+ sleep 500 &
$ jobs
[1] Running sleep 300 &
[2]- Running sleep 400 &
[3]+ Running sleep 500 &
$ kill %1
$ jobs
[1] Terminated sleep 300
[2]- Running sleep 400 &
[3]+ Running sleep 500 &
$ fg %2
sleep 400
^C
$ echo $?
130
$ jobs
[3]+ Running sleep 500 &
$ disown %3
$ jobs; ps -o pid,ppid,stat,comm -C sleep
PID PPID STAT COMMAND
2599 2596 S sleep
After disown, jobs no longer lists sleep 500, but the process keeps running as a child of the shell.
| Key or command | Effect |
|---|---|
Ctrl-Z | Stop the foreground job (SIGTSTP) |
bg | Resume the current job in the background (SIGCONT) |
fg | Resume it in the foreground |
Ctrl-C | Interrupt the foreground job (SIGINT) |
Ctrl-\ | Quit with a core dump (SIGQUIT) |
kill %n | SIGTERM to every process of job n |
wait | Block until all background jobs finish |
A background job that reads from the terminal receives SIGTTIN and stops at once:
cat &
jobs
Output:
[1] 6329
[1]+ Stopped cat
Give background jobs their own input
A job waiting on SIGTTIN does nothing until it is brought to the foreground. Redirect stdin from a file or /dev/null for anything started with &.
What Survives a Closed Terminal¶
Four background jobs were started in an interactive shell, then the terminal was closed, which sends SIGHUP to the shell.
sleep 601 &
nohup sleep 602 > /dev/null 2>&1 &
setsid sleep 603 &
sleep 604 & disown
ps -o pid,ppid,sid,tty,comm -C sleep
echo '--- after the terminal closed ---'
ps -o pid,ppid,sid,tty,args -C sleep
Output:
$ sleep 601 &
[1] 2645
$ nohup sleep 602 > /dev/null 2>&1 &
[2] 2646
$ setsid sleep 603 &
[3] 2647
$ sleep 604 & disown
[4] 2649
[3] Done setsid sleep 603
$ ps -o pid,ppid,sid,tty,comm -C sleep
PID PPID SID TT COMMAND
2645 2643 2643 pts/0 sleep
2646 2643 2643 pts/0 sleep
2648 1 2648 ? sleep
2649 2643 2643 pts/0 sleep
--- after the terminal closed ---
PID PPID SID TT COMMAND
2646 1 2643 ? sleep 602
2648 1 2648 ? sleep 603
2649 1 2643 ? sleep 604
| Job | Survived | Why |
|---|---|---|
sleep 601 & | No | bash forwarded SIGHUP, and sleep has the default action |
nohup sleep 602 & | Yes | SIGHUP is ignored |
setsid sleep 603 & | Yes | Different session; no terminal to hang up |
sleep 604 & disown | Yes | Not in bash's job table, so bash did not signal it |
setsid forked because the shell had made it a process group leader, so the job table reported Done for PID 2647 while the real sleep ran as PID 2648 in its own session.
nohup does not protect against a closed stdout
A nohup job that still writes to the terminal gets EIO or SIGPIPE after the disconnect. Redirect output to a file, as nohup cmd > job.log 2>&1 & does.
tmux¶
tmux keeps shells running inside a server process that is independent of the SSH connection. It is the practical choice for long interactive work on a remote host.
| Command | Effect |
|---|---|
tmux new -s deploy | New session named deploy |
Ctrl-b d | Detach |
tmux ls | List sessions |
tmux attach -t deploy | Reattach |
Ctrl-b c / Ctrl-b n | New window / next window |
Ctrl-b % / Ctrl-b " | Split vertically / horizontally |
tmux kill-session -t deploy | End the session |
Use a systemd unit for anything that must outlive a reboot
nohup, disown and tmux protect against a disconnect, not against a reboot or a crash. systemd-run --unit=<name> <cmd> gives a job logging, restart policy and a status command; see Systemd Toolbox.
Common Errors¶
bash: fg: current: no such job¶
Cause: the job table of this shell is empty: the job finished, was disowned, or belongs to another shell.
Fix: find the process with pgrep -a <name>; a process from another shell cannot be brought to this terminal, but reptyr <pid> can move it on some systems.
Interview Checkpoints¶
L1: What does Ctrl-Z do, and how do you continue the job?
Say first: it sends SIGTSTP and stops the foreground job; bg continues it in the background and fg in the foreground.
Proof: jobs shows Stopped, then Running after bg %1.
Follow-up: Which signal do bg and fg send? (SIGCONT.)
L1: Why does a background job die when the SSH session closes?
Say first: the hangup sends SIGHUP to the shell, which forwards it to its jobs, and the default action of SIGHUP is to terminate.
Proof: after closing the terminal, only the nohup, setsid and disown jobs remain in ps.
Follow-up: Which of those still dies if it writes to the closed terminal?
L1: What is the difference between Ctrl-C and Ctrl-D?
Say first: Ctrl-C sends SIGINT to the foreground process group; Ctrl-D sends end-of-file to a program reading the terminal.
Proof: sleep 100 then Ctrl-C gives $? 130; cat then Ctrl-D exits 0.
Follow-up: Why does Ctrl-D log out an empty shell?
L2: A long command is running in the foreground and the session must be left. Keep it running.
Say first: stop it, resume it in the background, and remove it from the job table.
Proof: Ctrl-Z, then bg, then disown -h %1 (or disown %1).
Follow-up: What happens to its output?
L2: Start a job that survives logout and keeps a log.
Say first: ignore SIGHUP and redirect every stream.
Proof: nohup ./backup.sh > backup.log 2>&1 < /dev/null &
Follow-up: How would you do it with systemd instead? (systemd-run --unit=backup ./backup.sh.)
L2: Kill every job the current shell started.
Say first: pass the job PIDs to kill.
Proof: kill $(jobs -p)
Follow-up: Why does this not touch disowned processes?
L3: A developer says their nohup job still stops after they disconnect.
Say first: check what the process receives and where its output goes.
Proof: grep SigIgn /proc/<pid>/status for bit 0; ls -l /proc/<pid>/fd/1 for a terminal device; journalctl --since for SIGPIPE or an OOM kill.
Follow-up: How does tmux avoid the problem?
Related¶
- Signals:
SIGHUP,SIGTSTPandSIGCONT - Process Lifecycle: sessions and process groups
- Shell Basics: terminals and shortcuts
- Systemd Toolbox:
systemd-runfor long jobs
Captured on Rocky Linux 10.2 (iximiuz Labs microVM, kernel 6.1.167), 2026-09.