Linux background jobs

Run a process in the background on Linux and keep it after logout

cmd & runs a command in the background, but it dies when the terminal closes or SSH drops. When we sent SIGHUP to bash 5.2, a plain & job and a stopped job died, while nohup (coreutils 9.7), disown and setsid jobs survived. tmux, screen and systemd-run --user also keep running after logout. None of them survive a reboot.

5 min readTerminals and remote servers

At a glance

  • cmd & frees your prompt but dies on a dropped connection.
  • nohup, setsid and disown survive a closed terminal; redirect the output.
  • systemd-run --user needs loginctl enable-linger to outlive your last logout.
  • tmux and screen are the only ones you can reattach to.

Which method survives what

When you close a terminal or your SSH connection drops, an interactive bash gets SIGHUP and passes it on to its jobs. We tested the methods below on bash 5.2, coreutils 9.7 and util-linux 2.41 by sending SIGHUP to an interactive bash.

MethodSurvives a closed terminal or SSH dropWhere the output goesReattach later
cmd &No; a clean exit spares it while huponexit is off, the defaultThe terminalOnly fg from the same shell
nohup cmd > log 2>&1 &Yeslog, or nohup.out without a redirectNo
cmd & then disownYes, if its output is redirectedThe terminal unless you redirect itNo
setsid cmd > log 2>&1 < /dev/null &YeslogNo
systemd-run --userYes, with loginctl enable-lingerThe systemd journalNo
tmux or screenYes, unless logind has KillUserProcesses=yesThe tmux or screen windowYes

None of these survive a reboot, and none restart the job if it crashes.

Background jobs with &, jobs, fg and bg

& at the end of a command runs it in the background of your current shell, while its output still goes to your terminal. If you started a command in the foreground and want your prompt back, press Ctrl-z to suspend it and run bg to resume it in the background.

Bash
./backup.sh > backup.log 2>&1 &
jobs -l
fg %1
# Ctrl-z suspends it again, bg resumes it in the background
bg %1
kill %1

jobs -l lists the current shell's jobs with their process IDs, and %1 means job 1. A job suspended with Ctrl-z shows as Stopped, not running, and in our test a stopped job died on SIGHUP.

A plain cmd & does not survive a dropped connection or a closed terminal, because bash sends SIGHUP to its jobs. On a clean exit it survives, because bash's huponexit option is off by default; with shopt -s huponexit in a login shell, such as an SSH session, it dies on exit too.

Keep it running after logout: nohup, disown and setsid

If you have not started the command yet, nohup cmd > log 2>&1 & makes it ignore SIGHUP. setsid cmd > log 2>&1 < /dev/null & starts it in a new session without a controlling terminal, so the hangup never reaches it. For a Python script, add -u or PYTHONUNBUFFERED=1, or the log fills in blocks.

Bash
nohup ./backup.sh > backup.log 2>&1 &
setsid ./backup.sh > backup.log 2>&1 < /dev/null &

# Already running as job 1? Keep it alive after logout
disown %1

If the job is already running under cmd &, or you paused it with Ctrl-z and resumed it with bg, disown keeps it alive after you log out: bash removes it from the job table and never sends it SIGHUP. disown -h %1 keeps it in the table but marks it so SIGHUP is not sent. Both survived our test.

disown does not redirect output. In our test, a disowned Python job that kept printing to the terminal died within 2.5 seconds of the terminal closing, from the write error, while the same job with its output redirected kept running. Every detail of nohup's output files is in the nohup guide.

Run it as a systemd user service

systemd-run --user hands the command to your systemd user manager as a transient service. The user manager stops after your last session ends unless lingering is on, so run loginctl enable-linger first if you are logging out.

Bash
loginctl enable-linger
systemd-run --user --unit=train --same-dir python3 -u train.py
systemctl --user status train
journalctl --user -u train -f
systemctl --user stop train

systemctl --user status train shows its state and recent output, journalctl --user -u train -f follows the output live, and systemctl --user stop train stops it. The service does not inherit your shell's environment, so pass variables with --setenv=NAME=value.

Or keep a terminal you can come back to

tmux and screen keep the job attached to a terminal you can reattach to later with tmux attach -t NAME or screen -r NAME. If systemd-logind kills user processes at logout (KillUserProcesses=yes, the upstream default that many distributions turn off), start them with systemd-run --scope --user and enable lingering. The tmux detach and attach guide has the full workflow.

Know when it finishes without logging back in

Every method above still makes you log back in to find out whether the job finished.

FAQ

What is the difference between nohup and disown?

nohup goes in front of the command when you start it, makes it ignore SIGHUP and sends its output to nohup.out if you do not redirect it. disown is a bash builtin you run after cmd &: it removes the job from the job table, or marks it with -h, so bash never sends it SIGHUP, but it does not redirect output.

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

In the shell that started it, run jobs -l and then fg %1 for job 1. After you log out, that shell and its job table are gone, so only tmux (tmux attach -t NAME) and screen (screen -r NAME) let you reattach.

How do I find my background process after logging back in?

jobs only lists jobs of the current shell, so it is empty in a new SSH session. Use pgrep -af name to find the process, or systemctl --user status train if you started it with systemd-run --user --unit=train.

Why did my background job die when I closed the terminal?

When the terminal closes or SSH drops, bash receives SIGHUP and passes it on to its jobs, which kills a plain cmd & job and a job stopped with Ctrl-z. A disowned job that still writes to the closed terminal can also die on the write error, so redirect its output.

How do I run a command in the background and save its output?

Redirect both streams before the trailing &, as in nohup cmd > log 2>&1 & or setsid cmd > log 2>&1 < /dev/null &. For a Python script, add -u or set PYTHONUNBUFFERED=1 so lines reach the log as they are printed.

Check on long runs from your phone

Change one import and every tqdm bar prints a link you can open on any device. Get a Discord or Slack message when the run finishes, fails or stalls.

Get a live linkpip install tqdm-live

Sources