Start a Command in a Separate Linux Session with setsid

A daemon you launched dies the moment you close the terminal that started it, because it inherited your shell's session. setsid is the fix: it runs a program in a brand new session, detached from the caller's session and process group. It is handy for small launch scripts, test processes and terminal-sensitive commands. You will finish knowing when setsid forks, how to capture the launched command's exit status, and when the terminal option is unsafe or unnecessary.

1. Confirm the command

Check that the executable is available and record its version:

command -v setsid
setsid --version

Expected output includes a path followed by a util-linux version line. The command accepts one program and then that program's arguments:

setsid program [argument ...]

Do not put a shell pipeline directly after setsid unless you deliberately want the shell to interpret it first. To run several shell operations as the new-session program, pass an explicit shell:

setsid sh -c 'printf "session work\n"; printf "finished\n"'

The two lines should be printed by the shell started through setsid. Quote the shell script as one argument so the calling shell does not execute it before setsid ever sees it.

2. Understand the default fork rule

Linux sessions contain process groups, and a process-group leader cannot call setsid(2) successfully. By default, setsid checks this condition: if it is already a process-group leader, it forks and lets the child create the new session; otherwise it runs the requested program in the current process.

That detail affects process IDs and parent-child relationships, but not the command's purpose: the requested program still ends up in a new session. If your caller needs a new process every time, request it explicitly:

setsid --fork sh -c 'printf "child pid: %s\n" "$BASHPID"'

Use --fork when the extra process is part of your launch design. Otherwise, leave the default alone: it avoids an unnecessary fork and is usually less surprising in scripts.

3. Preserve the launched command's exit status

Without --wait, the parent does not wait for a program started down the fork path, so a failing child looks the same as a succeeding one to your script. If you need to know whether the launched command succeeded, add --wait: setsid's own exit status then follows the executed program's:

setsid --wait sh -c 'exit 7'
printf 'setsid status: %s\n' "$?"

Expected output is:

setsid status: 7

This is the safer shape for a wrapper that must fail when its child fails. Test the status immediately, since the next command overwrites $?.

4. Choose terminal handling carefully

setsid normally creates a session with no controlling terminal. --ctty sets the controlling terminal to the current one, a terminal-management operation, not a general way to make a background command interactive. Only reach for it when the launched program specifically needs the current terminal and you know which terminal the caller owns:

setsid --ctty sh -c 'tty'

The output should be a terminal device when run from an interactive terminal. In a service, pipeline or redirected shell there may be no usable terminal at all, and the command can fail or behave differently. Do not add --ctty just because you saw it in an example.

Safety boundary: this guide does not use sudo, stop services or detach a production process. Adding --ctty can change where a program reads input and writes output, so test it against a harmless command before applying it to a long-running or interactive one. To undo the examples, let the short-lived commands exit; nothing persistent changed.

Common traps and recovery

Done means