Launching a long-running command with my-script.sh & and then closing the SSH terminal kills the script anyway, despite the & supposedly making it run in the background. & detaches the command from the shell’s foreground, but does nothing to protect it against what happens when the shell itself terminates.
What actually happens on disconnect
When a shell session ends (terminal closed, SSH connection dropped), the kernel sends SIGHUP (hangup) to every child process of that shell, background ones included. A process that doesn’t explicitly handle this signal terminates on receiving it, exactly like it would terminate on a SIGTERM with no trap to catch it.
# & backgrounds it, but does nothing at all
# to protect against SIGHUP on disconnect
my-long-script.sh &
exit
# → my-long-script.sh receives SIGHUP and terminates
nohup: explicitly ignoring SIGHUP
nohup launches a command with SIGHUP explicitly set to be ignored, before the process even starts: the shell disconnecting then has no effect on it at all, standard output being redirected by default to a nohup.out file since the terminal that displayed it won’t exist anymore.
# Immune to SIGHUP from launch,
# output goes to nohup.out by default
nohup my-long-script.sh &
disown: protecting a process already running
disown applies after the fact, to a process already backgrounded in the current shell: it removes that process from the shell’s job table, which stops it from receiving the SIGHUP the shell normally propagates to its children when it terminates.
my-long-script.sh &
disown
# The shell can now close,
# the process continues without receiving SIGHUP
setsid: the most radical protection
setsid launches a command in a brand new session, detached from any controlling terminal from the start: with no controlling terminal, there’s structurally nobody to send SIGHUP to that process, a stronger guarantee than nohup (which ignores the signal) or disown (which prevents its propagation), since the relationship that would produce that signal simply never exists.
Takeaway
& detaches a command from the shell’s foreground but doesn’t protect it against SIGHUP, the signal the kernel sends to every child of a shell when it terminates, backgrounded ones included. nohup makes that signal ignored from launch, disown removes an already-running process from the job table afterward to stop it receiving it, and setsid offers the strongest guarantee by detaching the process from any controlling terminal from the start. The right tool depends on timing: before launch for nohup or setsid, after the fact for disown on a process already running.