Frontend Labs
⋯

Chapter 32 — Job control: foreground, background, and nohup

In one sentence

A shell can run commands in the foreground (you wait for it) or the background (it runs while you keep typing), and understanding exactly what happens to a background job when your terminal session ends is the difference between a smooth long-running task and a lost one.

Why this matters

Starting a multi-hour data migration over SSH, then having your connection drop and the job silently die with it, is one of the most common, most avoidable real production frustrations — and it's entirely explained by Chapter 20's signals.

How it actually works

   command &            # run in the BACKGROUND -- your shell keeps
                        # control immediately, doesn't wait
   jobs                  # list your shell's current background/stopped
                        # jobs
   fg %1                  # bring job 1 to the FOREGROUND (wait for it)
   bg %1                   # resume a STOPPED job in the background
   Ctrl-Z                  # SUSPEND (STOP, Chapter 5's `T` state) the
                        # current foreground job
   Ctrl-C                  # send SIGINT to the current foreground job
                        # (Chapter 20)

   nohup command &         # run in the background, AND ignore SIGHUP --
                        # so it survives your terminal/SSH session
                        # ending
   disown %1                # remove a job from your shell's job table
                        # WITHOUT killing it -- it keeps running, but
                        # your shell stops tracking it

Why a background job can still die when you log out: when your SSH session (or terminal) closes, the shell process itself is typically sent SIGHUP ("hang up" — a name dating back to actual telephone modems). By default, many programs' handling of SIGHUP (or simply inheriting the shell's own death) means background jobs started with a plain & can be killed too, unless something protects them — which is exactly nohup's job.

Real-world example

   "I started a long-running migration/backup/build over SSH, my
   connection dropped, and the job died with it" is one of the single
   most common, most universally-experienced real frustrations in
   day-to-day Linux/ops work -- documented in essentially every "Linux
   tips" resource and endlessly rediscovered by engineers the hard way,
   usually mid-incident, at the worst possible time. The mechanism is
   exactly this chapter's: the SSH session closing sends SIGHUP toward
   the shell and, often, its children; a plain background job (`&` with
   no protection) can die right along with it.
   THE REAL, PRACTICAL FIXES, all addressing the same root cause:
     `nohup long_command &`        explicitly ignore SIGHUP
     `disown`                       detach an ALREADY-running job from
                                   the shell's job table
     `screen` / `tmux`              (properly) run the command inside a
                                   TERMINAL MULTIPLEXER session that
                                   keeps running independently of your
                                   SSH connection entirely -- you can
                                   disconnect and RE-ATTACH later, seeing
                                   the still-running session exactly as
                                   you left it. This is the tool most
                                   experienced engineers actually reach
                                   for by default for anything long-
                                   running, precisely because it survives
                                   network drops, not just deliberate
                                   logouts.

Try it yourself

# See job control directly
sleep 100 &
jobs                    # shows [1]+ Running   sleep 100 &
fg                       # brings it to foreground -- now press Ctrl-C
                         # to actually stop it (or wait, or Ctrl-Z)

sleep 100 &
kill -STOP %1            # simulate Ctrl-Z from a script
jobs                      # shows Stopped
bg %1                      # resume it in the background
jobs                        # now Running again
kill %1                      # clean up

# See nohup protect a job from SIGHUP (simulated, safely)
nohup sleep 60 > /tmp/nohup_test.log 2>&1 &
echo "PID $! is protected from SIGHUP"
kill $!                      # clean up

# If tmux is installed, try it
tmux new -s demo 'echo "inside tmux"; sleep 5; echo done' 2>/dev/null || \
  echo "(tmux not installed -- worth adding for real long-running work)"

Common mistakes

  • Assuming & alone is enough for a job that must survive logout. It isn't, by default — pair it with nohup, disown, or better, run it inside screen/tmux from the start.
  • Forgetting fg/bg need a job NUMBER (%1) if you have more than one. With only one background job, plain fg/bg works; with several, you need to specify which.
  • Starting a critical long-running task directly over a plain SSH session "because it'll probably be fine." For anything that takes more than a few minutes, use tmux/screen as a default habit, not an afterthought.

Check yourself

  1. What signal typically threatens a background job when your SSH session closes, and why?
  2. What does nohup actually do?
  3. Why do experienced engineers often prefer tmux/screen over nohup for long-running work?

Further reading

  • Reference: man 1 nohup, man 1 jobs, man 1 disown, man 1 tmux.
  • Book (free): The Linux Command Line — the job-control chapter covers this cleanly with worked examples.
  • Guide: any good tmux cheat sheet — worth genuinely learning, not just skimming, given how often it pays off.

Generated from real-life-os-guide.md · 8 Oct 2026 16:36