$RodHat_
Console Tips

nohup, disown, setsid: what each actually does and which one you want

Published by

nohup, disown, setsid: what each actually does and which one you want
Photo: AI-generated — no human photographer / RodHat AI Cover

Every few months someone asks why their process died when they closed the SSH session, and someone else tells them to use nohup. Then the process dies again. Then they try disown. Sometimes that works, sometimes it doesn’t, and now there are two confused people instead of one.

Here is the actual model. It takes five minutes to understand and you will never be confused about this again.

Why closing the terminal kills your process

The terminal is a session. When your shell starts, it becomes a session leader. Every process you launch from that shell inherits the session, and the terminal device (/dev/pts/N) is the session’s controlling terminal.

When you close the terminal — close the window, kill the SSH daemon, drop the connection — the kernel sends SIGHUP to the foreground process group of the controlling terminal. The shell receives SIGHUP and, by default, forwards it to every process in its job table before exiting. Most processes do not handle SIGHUP and die with the default action: terminate.

So there are two separate ways a process dies when you close the terminal:

  1. The kernel sends SIGHUP directly to the foreground process group.
  2. The shell sends SIGHUP to every job in its table before it exits.

These are not the same signal from the same source, and nohup, disown, and setsid each address a different subset of this problem.

nohup

nohup ignores SIGHUP before execing the process:

nohup ./my-server &

What it actually does:

  • Sets SIGHUP to SIG_IGN before exec
  • Redirects stdin from /dev/null (so the process cannot accidentally read from the terminal)
  • Redirects stdout and stderr to nohup.out if they’re not already redirected to a file

What it does not do: remove the process from the shell’s job table. The process is still in the same session. The shell still knows about it, and on some shells will still forward signals to it. It does not create a new session.

This means: if the shell itself receives SIGHUP and forwards it to its children before they have a chance to exec with the SIG_IGN already set — or if the process resets signal handlers with signal(SIGHUP, SIG_DFL) at startup — the process dies anyway.

nohup is the minimum viable dodge. It works well enough for quick one-offs. For anything you actually care about, keep reading.

disown

disown removes a job from the shell’s job table:

./my-server &
disown %1
# or disown by PID:
disown 12345
# or disown all jobs:
disown -a

What it actually does: removes the job from the internal table that bash/zsh maintains. When the shell exits, it only forwards SIGHUP to processes in its job table. Remove the job, the shell won’t signal it.

What it does not do: change SIGHUP disposition, remove the process from the session, or detach the controlling terminal.

So if the kernel sends SIGHUP directly (because the terminal device is closed), the process still gets it — assuming it’s handling signals normally. disown protects against the shell’s forwarding behavior, not the kernel’s direct delivery to the foreground process group.

The common pattern:

./my-server &
disown %1

Works well in practice because backgrounded processes (&) are not in the foreground process group, so the kernel’s SIGHUP on terminal close targets the shell (foreground), not the backgrounded process. The shell then doesn’t forward it because you disowned the job. This is why the pattern appears to work reliably — but it’s two separate protections coincidentally combining, not disown solving the problem on its own.

setsid

setsid creates a new session:

setsid ./my-server

What it actually does: calls setsid(2) before exec, which:

  • Creates a new session with the process as session leader
  • Removes the controlling terminal entirely — the new session has no controlling terminal
  • Moves the process into a new process group in that new session

A process with no controlling terminal cannot receive SIGHUP from a terminal hangup. The kernel has nowhere to send it. You’ve done the actual thing, not the workaround.

This is the correct approach for anything that needs to survive independently of your terminal. It is also what a proper daemon startup does — fork, setsid, fork again (the double fork is to ensure the process is not a session leader and therefore cannot accidentally acquire a controlling terminal if it opens a terminal device later), redirect stdio to /dev/null, close all inherited file descriptors.

For a quick long-running process you want genuinely detached:

setsid ./my-server </dev/null >>/var/log/my-server.log 2>&1 &

The </dev/null closes stdin, the append redirection gives you a log, and the & puts it in the background immediately. The setsid makes it truly orphaned from your session.

The combination that actually covers all cases

nohup setsid ./my-server </dev/null >>/var/log/my-server.log 2>&1 &
disown %1

Overkill for most situations, but if you want belt-and-suspenders:

  • setsid gives it a new session with no controlling terminal
  • nohup sets SIGHUP to SIG_IGN in case the process resets it or you didn’t use setsid
  • disown removes it from the shell’s job table
  • </dev/null >>/var/log/... 2>&1 closes stdin and captures all output

In practice, setsid alone is sufficient for processes that don’t mess with their own signal handlers. Add nohup if you’re dealing with software you don’t control that might do something interesting at startup.

What to use when

GoalTool
Quick one-off, don’t want it killed when I log outnohup cmd &
Already backgrounded, shell is nagging about jobsdisown %N
Proper long-running process, decoupled from terminalsetsid cmd </dev/null >>log 2>&1 &
Anything that needs real uptime and restart behaviorsupervision without systemd

For processes that need to stay up across reboots, restart on failure, and log properly: use a supervisor. setsid is not a process manager. It does the detachment and then gets out of the way — after that, if the process crashes, it’s gone.

Reading the session and process group

Verify what you’ve done:

# Show session ID and process group for a PID
ps -o pid,ppid,sid,pgid,comm -p 12345

# Show the controlling terminal (or ? if none)
ps -o pid,tty,comm -p 12345

A process successfully detached with setsid shows ? under tty. A process that still has your terminal shows /dev/pts/N.

# List all processes in a session
ps -eo pid,sid,comm | awk -v sid=YOUR_SESSION_ID '$2==sid'

Replace YOUR_SESSION_ID with the output of echo $$ from your shell (that’s the shell’s PID, which is also the session ID).

The double fork, briefly

Classic UNIX daemons fork twice. After the first fork, the child calls setsid and becomes a session leader. But a session leader can acquire a controlling terminal by opening a terminal device. The second fork creates a child that is in the new session but is not the session leader — and non-session-leaders cannot acquire a controlling terminal. The result is a process that is definitely, permanently disconnected from any terminal.

If you’re writing a daemon in C, you do this yourself. If you’re writing a shell one-liner, setsid handles the first part; the double-fork is a belt-suspenders-and-a-rope-belt concern you can mostly ignore unless you’re opening terminal devices yourself.

The full treatment of sessions, process groups, controlling terminals, and the POSIX job control model is in Stevens’ Advanced Programming in the UNIX Environment — Chapter 9, which covers process relationships. It’s the clearest explanation of why job control works the way it does and what the kernel is actually doing when you hit Ctrl-C. Kerrisk’s The Linux Programming Interface covers the same ground with more Linux-specific detail in Chapters 34 and 38.