I was helping a colleague who was having problems with a background process intermittently dying.

I found out that they were starting the background process by logging in to the server and executing:

su - <user> -c '<command>' &

"Aha", I exclaimed. "If you start a command with "&" it will hang-up when you exit the controlling terminal. You need to use something like nohup to achieve this. Really this process needs to support running as a daemon, tut tut."

We tested the above command to demonstrate my point and... it seemed to work: the process started by command didn't exit when the we exited the terminal that ran the above command.

command is a custom Python script whose output goes to a file. As far as I can tell there is no intelligent "daemonize" like capability in the script. It doesn't do any of the things needed to run as a daemon listed here 1

Running the command like so behaves as expected:

<command> &

In the above case the background process started by command exits when we exit the terminal.

My question is this:

  1. What is happening when we add "su - -c &" that prevents the process from exiting when our terminal exits. I would like to understand in detail with respect to the controlling terminal, standard input and output etc.

  2. Is this a reasonable way to achieve the goal of running this command as a background process. If not why, not?

I want to propagate best practice within my company, but I need to be able to demonstrate and back-up any recommendations I make.

Also, I just want to understand what is going on exactly.


There are several ways a process might be killed because of a dying terminal.

  1. The first way is that the terminal driver in the kernel sends a SIGHUP signal to the controlling process for which the terminal is the controlling terminal. In most cases, the controlling process is the shell that is initially started in the terminal, and its controlling terminal is the one that its stdin, stdout and stderr are connected to. A process becomes disconnected from the controlling terminal if it calls setsid.

  2. The second way is that the shell, when it receives SIGHUP, re-sends that signal to its subprocesses — more precisely, to its background jobs. Some shells (ksh, bash, zsh) have a disown builtin to tell the shell not to send a SIGHUP to a particular job.

  3. If the terminal goes away and a program tries to read from it, it is notified of an end-of-file condition (or possibly EIO for a background job). If the terminal goes away and a program tries to write to it, the write returns with an EIO error. These might cause the program to exit.

So what su changes here is that (2) would normally cause the initial shell to kill the background job, but since that background process is running as a different user, the signal cannot be delivered, and the background process lives on.

  • I've seen root user do chroot --userspec root:root / sh -c "exec some_forever_process" &. Job is running as same user, with no explicit nohup before or disown later. So for this case, how is it the signal cannot be delivered on term exit? – Toddius Zho Feb 15 '18 at 0:48
  • @ToddiusZho Presumable some_forever_process was intended to run forever (a daemon) and so protects itself against receiving SIGHUP from a terminal exit (by running in its own process group). – Gilles Feb 15 '18 at 17:33
  • bash/tail doesn't protect against SIGHUP, yet that still works: ``` local$ ssh root@REMOTE_HOST remote# chroot --userspec root:root / sh -c "exec bash -c 'tail -f /dev/null'" & [1] 6376 remote# ps -p $! --no-heading -o pid,uid,command -ww 6376 0 tail -f /dev/null remote# exit local$ ssh root@REMOTE_HOST remote# ps -p 6376 --no-heading -o pid,uid,command -ww 6376 0 tail -f /dev/null ``` – Toddius Zho Feb 16 '18 at 19:02

Your Answer

