I am trying to start a php (under apache) process (by calling the apache from a browser), that will survive shutting down the apache server (sudo service apache2 stop). Even when I make sure the created process has no parent (parent 1), and has its own session, still, somehow, the process is died when I stop the apache (or restart the apache)
I created a test.php file:
<?php
exec('setsid nohup sleep 1000 > /dev/null 2>/dev/null &');
?>
When running doing HTTP GET to this test.php, indeed we get an immediate OK response, and the process still lives.
But, when we do:
sudo service apache2 stop
The sleep process dies.
How can someone kill a process when the process doesn’t belong to its group or session, and when the process is not a child?
6
Answers
Apache will probably have a list of the spawned processes and kills them individually, and not as a group. In that case, all processes in the list will be
kill(2)
ed. But see below next paragraph for a possibility.Look at the man pages for the
kill(2)
system call. In the ERRORS section, the only possibilities to fail are:EINVAL
, meaning an invalid signal number has been passed. Doesn’t apply here.ESRCH
, the process (or process group) doesn’t exist. Doesn’t apply also.EPERM
, you don’t have permission to send a signal. This applies here, but the only processes (and this has nothing to do with process hierarchies or parental relationships) you are allowed to send signals to are the processes that run with real/saved user id equals the effective user id of the sender process. So, as Apache has a registry of all the processes it launches, it is normal that it is able to kill the process.Anyway, have you tried to create a process, from that process create a subprocess, and execute the
setsid
in the grandchild subprocess? That way, there’s no chance for the Apache process to have it registered in the list of spawned processes. I have not tried that, but it could work.From the FreeBSD
kill(2)
manual page:(emphasis is mine) in linux, it’s almost the same, except
but this doesn’t apply here.
List all active processes (e.g.
ps aux
). The processes, which were created by Apache run as userwww-data
.When you stop Apache service, I suspect they will be terminated based on that or some internal list.
apache2.service stop
calls apachectl -k graceful-stop. This will SIGTERM all "child processes". Unfortunately, I was unable to find the exact place in the code. Maybe someone could do that to verify the hypothesis.A solution:
Run the process as another user. How you go about that depends on your case. You will have to define some kind of interface.
For example, you could have another process listen on
localhost
or use a Unix domain socket. The same will be achieved by using gearman, as @Akshay Vanjare pointed out in the comments.Your PHP script can then call to the interface.
No solutions:
www-data
you cannot start a process as another password protected user, because su asks for pass and switching UID is not a good idea.su - NEW_USER -c "COMMAND"
in PHP script. Do not do this in any system you care about. It is insecure. Very insecure. And you will have a new process every time the script is called. You would have to take care to kill it.Further thoughts:
I also tried a few alternatives to "nohup" the command, such as daemonizing, fork(), disown etc. They did not work for me.
I did not try hard in the new process to ignore SIGTERM. maybe it is possible to solve the question that way, too.
To me, it makes sense for Apache to do (aggressive) clean up when it is stopped. It is behavior I would expect from a web browser, which has to handle a lot of children.
First about my answer, it’s not a good idea to control dameon from the web.
Once this said, you can have a php script that write a flag or somthing like that either on a database, a file, redis, etc.
On the other side make a PHP script that you schedule with cron for looking for the flag. If found, the script can start a PHP daemon that will detach. Pay attention to running the cron script and the PHP daemon with secured user & rights.
But once again be carefull with security concern.
I’m curious on why the apache server needs to be shut down, but im inclined to say you might try a different strategy altogether using a containerized solution. Exposing an nginx docker container that can trigger your php process would likely be more stable than using Apache, as the docker daemon always runs as root. I think it depends on the specific needs of your use case though, so explaining why you need to shut down Apache might get you better answers.
From the PHP developer perspective I don’t think there is a way to achieve that.
Or you can implement a queue and worker to do your tasks/executions.
and for that you can either use Redis or any other database.
https://laravel.com/docs/9.x/queues
You can write a service that then you can control with
sudo service start|stop|restart
You can use
visudo
to edit sudoers file to allowwww-data
to usesudo
to run a specificsh
file, without being prompted by password. This sh file will contain the lineservice your_service start
.Example: I wanted to be able to restart apache2 from a client side ajax request.
Context: file server with no screen/keyboard attached. I have SSH setup, but I don’t want to launch a client in my laptop. I protect the restart page with apache password protected directory, along with other administrative pages. For example: I have a page to restart/shutdown the whole system.
I did this:
Register a service to run with
sudo service apache_restarter_service start
This service file calls a simple
sh
script, a two lines script:sleep 1 second and then call service apache2 restart
.Use
visudo
to edit sudoers file. Allow userwww-data
to usesudo
to run a sh script that callsservice apache_restarter_service start
.The service file is used only to start a process with root privileges, and it doesn’t need to be long lived, so no need for
Restart=always
in service definition. This process cannot be killed by apache and apache won’t even know about it. It will only know that thesh
script returned 0.Example of changes to sudoers file:
from_php.sh
only callsservice apache_restarter_service start
. No need forsudo
here, because your php script already used sudo when calling from_php.sh.from_php.sh
is needed here, because you don’t want php to callservice
directly. Because a BUG in, or an ATTACK against, your script may do harm to your server. This way, we only authorizedfrom_php.sh
in sudoers.The php script must call
from_php.sh
withsudo
. You can useexec("sudo /opt/apache_restarter/from_php.sh")
. An&
at the end is not needed, because apache won’t be restarted immediately.Example of service definition file
apache_restarter_service.service
:To avoid the service to run once the system is restarter. No harm really, but also useless. You can have the service always disabled and enable it in
from.php
script before starting it. Or just tune the service file to not run one time at system start.You can tune
StartLimitIntervalSec=0
to your needs.You can tune
User=root
to a safer, less privileged user. In my case I don’t care but maybe you do. You may want to usewww-data
. I don’t know why that may not work, but I didn’t tried to usewww-data
so far.You can write
run.sh
in many ways and to do different tasks. The simpler of which would be to simply wait 1 second and then restart apache withservice apache2 restart
.While my interest is to be able to restart apache from client side, this also applies to spawn a process that won’t be terminated when apache is stopped/restarted.
I also tried with
nohup
,&
,setsid
and whatnot. I will save people some time. Nothing works. Apache should be terminating all child processes as already suggested.Update 2022-12-04
I got into the following situation: need to start a python3 web server into alternate port to provide file server statistics.
Why? This is the concept, if nobody interested in the statistics (iostat, ifstat, etc) then the stats_server.py is not even running (wasting RAM/CPU). So, having stats_server.py starting with system is overkill.
Once a user visit
http://FILE_SERVER_IP/stats_launch.php
, or from JavaScript fetch, the programstats_server.py
must be launched and listen inhttp://FILE_SERVER_IP:ALTERNATE_PORT
. You may also need to handleCORS
headers to be able tofetch
from the alternate port.Also, it keep running and updating the stats with a second, or half second, interval, and saving to its own memory, so it can calculate averages or rates per second. Doing this server side gives more precision and less latency that just send the raw samples to client side.
This URL can be visited directly (you will see the output of
iostat
andifstat
, and values calculated by the python script, in plain text format), or user can loadFILE_SERVER_IP/stats.html
, where afetch
from JavaScript will periodically load that text plain output, and parse into nice HTML/CSS.You may think that everything can be solved by
fork
or by usingos.getpid()
,os.getsid()
andos.setsid()
inside the python script, to detach it from the original session and avoid termination with parent process (search stackoverflow for use cases) and it will if yourwww-data
user has the right permissions.Otherwise I still recommend the
visudo
approach above. You still need to detach if you start your python file from a .sh file, the difference is that now you have the permissions to do so.