PHP Daemons Tutorial

Foreword

The following is a tutorial for creating PHP scripted daemons.  All examples will be performed on CentOS linux.  The daemon code itself will be written in PHP with a BASH script wrapper.

Getting Started

So to get started, let’s create a simple PHP file that will be executed as our daemon. Essentially, we’re going to create your everyday run of the mill command line PHP file.  Then we’ll worry about turning into a daemon a little later.  For this example, let’s create a simple PHP script that put’s text into a log file every second.  So let’s create a file called “Daemon.php” and let’s put the following in it:

#!/usr/bin/php
 
<?php
 
while(true){
    file_put_contents('/var/log/Daemon.log', 'Running...', FILE_APPEND);
    sleep(1);
}//end while
 
?>

Ok, that wasn’t so bad.  The first line tells the interpreter what to execute the file against, in this case we want the file to be interpreted as PHP.  Next we create a simple infinite loop that writes “Running…” to the “/var/log/Daemon.log” file, then sleeps for a second.  Now let’s test it, but first we need to make it executable.

user@computer:$ chmod a+x Daemon.php

Now let’s test it.

user@computer:$ ./Daemon.php

Now that the script is running let’s check the log file.  Open a new terminal and issue the following command to verify the output.

user@computer:$ tail -f /var/log/Daemon.log

If all has gone well you should see live updates that read “Running…Running…Running…”.

Now, let’s enhance the script a little to make it more user friendly.  Let’s add the ability to pass in command line arguments and display a help message.

#!/usr/bin/php
 
<?php
 
$log = '/var/log/Daemon.log';
 
/**
 * Method for displaying the help and default variables.
 **/
function displayUsage(){
    global $log;
 
    echo "n";
    echo "Process for demonstrating a PHP daemon.n";
    echo "n";
    echo "Usage:n";
    echo "tDaemon.php [options]n";
    echo "n";
    echo "toptions:n";
    echo "tt--help display this help messagen";
    echo "tt--log=<filename> The location of the log file (default '$log')n";
    echo "n";
}//end displayUsage()
 
//configure command line arguments
if($argc > 0){
    foreach($argv as $arg){
        $args = explode('=',$arg);
        switch($args[0]){
            case '--help':
                return displayUsage();
            case '--log':
                $log = $args[1];
                break;
        }//end switch
    }//end foreach
}//end if
 
//the main process
while(true){
	file_put_contents($log, 'Running...', FILE_APPEND);
	sleep(1);
}//end while
 
?>

So now we have an elegant way to pass in command line arguments or options to our daemon process along with a nice display usage function which you can use to brand your process with author info, etc.  Now that’s all fine and dandy, but what does that have to do with creating a PHP daemon?  Hold on, we’re getting to that.  Passing in command line flags easily and elegantly will come in hand once we get our daemon up and running.  So now that we have a basic daemon process, let’s actually get it working as a daemon.  To do this we’re going to need a BASH daemon launcher, and we’ll eventually need to put it in the “/etc/init.d” directory.  So now the BASH daemon controller.

BASH Daemon Controller

Let’s start by creating a file called “Daemon” which we’ll use to control our “Daemon.php” file.  So first I’ll give you the entire BASH script, then I’ll explain it.

#!/bin/bash
#
#	/etc/init.d/Daemon
#
# Starts the at daemon
#
# chkconfig: 345 95 5
# description: Runs the demonstration daemon.
# processname: Daemon
 
# Source function library.
. /etc/init.d/functions
 
#startup values
log=/var/log/Daemon.log
 
#verify that the executable exists
test -x /home/godlikemouse/Daemon.php || exit 0RETVAL=0
 
#
#	Set prog, proc and bin variables.
#
prog="Daemon"
proc=/var/lock/subsys/Daemon
bin=/home/godlikemouse/Daemon.php
 
start() {
	# Check if Daemon is already running
	if [ ! -f $proc ]; then
	    echo -n $"Starting $prog: "
	    daemon $bin --log=$log
	    RETVAL=$?
	    [ $RETVAL -eq 0 ] && touch $proc
	    echo
	fi
 
	return $RETVAL
}
 
stop() {
	echo -n $"Stopping $prog: "
	killproc $bin
	RETVAL=$?
	[ $RETVAL -eq 0 ] && rm -f $proc
	echo
        return $RETVAL
}
 
restart() {
	stop
	start
}	
 
reload() {
	restart
}	
 
status_at() {
 	status $bin
}
 
case "$1" in
start)
	start
	;;
stop)
	stop
	;;
reload|restart)
	restart
	;;
condrestart)
        if [ -f $proc ]; then
            restart
        fi
        ;;
status)
	status_at
	;;
*)
 
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
	exit 1
esac
 
exit $?
exit $RETVAL

Ok, so the above BASH script is the daemon controller responsible for starting, stopping, restarting, etc.   Initially it sets itself up for use with chkconfig so that it can be set to start when the OS boots, etc.  Next it includes the basic BASH functions include file.  Afterward we check to see if the executable file actually exists before we try and start it.  Next, we set up the default variables for our program including the proc filename, bin or executable and the program name.  Next, we define some basic default parameters to pass to the PHP file when starting up.  You could also modify this script to read variables from an “/etc/Daemon” configuration file, but for now we’ll just set them directly in the BASH file.  Lastly the PHP file is invoked along with the daemon command.  The rest of the file simple reads the users input to the BASH file and handles start, restart, etc.  Next, let’s make the BASH Daemon file executable so we can use it in just a bit.

user@computer:$ chmod a+x Daemon

PHP Daemon

So now that we have our controller, we’ll need to modify our PHP script to work in a daemon process.  To have our PHP working in a daemon process we’ll need to use fork, that way we can establish a child process which will continually run and return a value to our controller to let it know that we were able to start properly. Here’s the modification to the PHP file.

#!/usr/bin/php
 
<?php
 
$log = '/var/log/Daemon.log';
 
/**
 * Method for displaying the help and default variables.
 **/
function displayUsage(){
    global $log;
 
    echo "n";
    echo "Process for demonstrating a PHP daemon.n";
    echo "n";
    echo "Usage:n";
    echo "tDaemon.php [options]n";
    echo "n";
    echo "toptions:n";
    echo "tt--help display this help messagen";
    echo "tt--log=<filename> The location of the log file (default '$log')n";
    echo "n";
}//end displayUsage()
 
//configure command line arguments
if($argc > 0){
    foreach($argv as $arg){
        $args = explode('=',$arg);
        switch($args[0]){
            case '--help':
                return displayUsage();
            case '--log':
                $log = $args[1];
                break;
        }//end switch
    }//end foreach
}//end if
 
//fork the process to work in a daemonized environment
file_put_contents($log, "Status: starting up.n", FILE_APPEND);
$pid = pcntl_fork();
if($pid == -1){
	file_put_contents($log, "Error: could not daemonize process.n", FILE_APPEND);
	return 1; //error
}
else if($pid){
	return 0; //success
}
else{
    //the main process
    while(true){
        file_put_contents($log, 'Running...', FILE_APPEND);
        sleep(1);
    }//end while
}//end if
 
?>

You’ll notice that I’ve added a few more calls to file_put_contents() just to let us know how we’re doing.  Now for the guts of the operation, we call pcntl_fork() to generate a child process and to let the parent caller return a value back to the BASH daemon controller.  The first if determines if the fork call worked at all, if it returns a -1, then it’s a failure, report the error and return a failed status back to the BASH daemon controller.  If $pid contains a valid number, then we’re good to go, but we’re still in the parent process, so we return 0 to let the BASH daemon controller know that all is well in the universe.  The else executes when the child process is created, and this is where the main part of our program executes.

Now, if all has gone well we should be able to start the daemon in normal daemon fashion.  If you’re running this in your /home/{username} directory then execute the following:

user@computer:$ ./Daemon start

You can also copy the Daemon BASH script to the “/etc/init.d/” directory, in which case you can start the daemon using the “service” command:

user@computer:$ service Daemon start

Stopping Daemon:              [ OK ]

Now to verify that everything is working correctly.  First let’s check the log file:

user@computer:$ tail -f /var/log/Daemon.log

Status: starting up.
Running…Running…Running…

Yep, all good there.  Now let’s check our process.

user@computer:$ ps ax | grep Daemon

14886 pts/0    S      0:00 /usr/bin/php /home/godlikemouse/Daemon.php --log=/var/log/Daemon.log
14944 pts/2    R+     0:00 grep Daemon

Ok, the process is running correctly.  Now, let’s stop it and verify again.  Issue the following command:

user@computer:$ ./Daemon stop

or if you copied the controller to the “/etc/init.d” directory

user@computer:$ service Daemon stop

Stopping Daemon:              [ OK ]

Now let’s verify that our process has stopped:

user@computer:$ ps ax | grep Daemon

14997 pts/2    R+     0:00 grep Daemon

Yep, all good…and there you have it.  Now you can write PHP daemons until you turn blue and pass out.  Hopefully this tutorial has been helpful, good luck.

The files used in this tutorial are available for download and use:

Download Project Files

36 thoughts on “PHP Daemons Tutorial

  1. Pingback: Daemon not closing in CentOS » Mentalist Nuno

  2. Nilesh

    Hello GodLikeMouse,

    Thank you for wonderful article about deamon script and it function.I have used daemon script to convert audio files in bulk so it give me better performance over cronjob process.
    I have set up that daemon script few months ago and it working well but sometime “Daemon” process terminated automatically. I have checked that it recently and it seems “Daemon” process is display with “$ ps ax | grep Daemon” command but PHP functional process don’t execute also checked that PHP script and there is not any issue. But somehow “Daemon Process” terminated or kill frequently from last few days.

    I have also add process command output as below.

    $ ps ax | grep Daemon

    webmast+ 24491 23504 0 07:51 pts/1 S+ 0:00 grep –color=auto Daemon
    root 32468 1 5 Jun29 ? S 26:16 /usr/bin/php /var/www/html/project/daemon/Daemon.php –log=/var/log/Daemon_live.log

    Can you please suggest what cause this to terminated or kill daemon process and is there any way that we can add error handling on deamon process.

    Thank You

    Reply
    1. godlikemouse Post author

      Hi Nilesh,

      This one is a little harder to track down. There could be something inside the code being executed by the Daemon that is causing the issue, it could also be memory usage or perhaps a memory leak causing the termination. Perhaps logging the internals of the application running inside the Daemon wrapper might tell you more. Try logging out that information to a file and see what’s happening and where/when it stops working.

      Reply
  3. Florian

    Hi, i have problem when i want to put ./daemonstarter.php , it’s name of my file.

    linux said this answer:

    ./daemonstarter.php: ligne1: ?php: Aucun fichier ou dossier de ce type
    ./daemonstarter.php: ligne3: Erreur de syntaxe près du symbole inattendu « “SEP”, »
    ./daemonstarter.php: ligne3: `//define(“SEP”, “/”);’

    can you help me plz?

    Reply
    1. Florian

      I have repair this problem but now i have

      bash: ./php : /usr/bin/php : mauvais interpréteur: Trop de niveaux de liens symboliques

      thx for help

      Reply
      1. godlikemouse Post author

        Hi Florian,

        It looks like you have something wrong in your first line of the deamon php file. The first line containing #/usr/bin/php etc. Your best bet would probably be to run:

        whereis php

        Use the result as the first line of your daemon php file and be sure you have the executable bit set on the file (ie. chmod +x myfile.php).

        Reply
  4. scott

    Just wanted to say thanks for posting this, i now have a daemon script that is constantly scanning stuff. Its working great.

    Reply
  5. lorus

    Hi I’m getting

    ./Daemon start
    Starting Daemon: daemon: unrecognized option ‘–log=/var/log/Daemon.log’

    How can I prevent daemon to parse the option itself and pass it to the binary instead?

    Reply
    1. godlikemouse Post author

      Hi lorus,

      Can you share a fiddle of your code or something similar. I’d need to see what you currently have to find out why you’re getting the error. You should be able to pass whatever flags you want to the PHP file.

      Reply
    1. godlikemouse Post author

      Hi Jerome, Your best bet will be to just write it yourself. It’s relatively easy, especially if you have a look at one of the existing service files or the example I have provided under BASH Daemon Controller.

      Reply
  6. fyrye

    Hi,

    Starting works fine but I had some issues with status and stop that took me a bit to troubleshoot.
    I was able to resolve the issue by changing the shebang to the php binary instead of env binary
    EG: #!/usr/bin/php

    Have any clue as to why running php from env would cause issues with the daemon stop/status?
    I checked $PATH and it lists /usr/bin
    > /usr/bin/env php -v shows me the version number

    Reply
    1. godlikemouse Post author

      It could be caused by the service trying to execute the php under an altered environment when under env or it could be a permissions issue.

      Reply
  7. Arnaud

    Hi,

    I would like to suggest some addition to your post.

    There are a few more steps that are usually performed after the initial fork call to set up the daemon process:

    – change the file mode mask (usefull to make sure the daemon can read and write to its log files):

    umask(0);

    – Create a new session id for the child process:

    $sid = posix_setsid(); // don’t forget to check that $sid >= 0

    – Change the current working directory to root (so that there is no trouble if the current directory is deleted or renamed)

    chdir(“/”); // don’t forget to check that return val is >= 0

    – Close the standard file descriptors

    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);

    After all that, you should be in real good shape to start your daemon logic. 🙂

    Reply
  8. bhu Boue vidya

    hey godlike mouse, thanks heaps for the post. highlighted the one main thing i hadn’t come across in other posts, which is forking the php script to return a code to the controller. cool!

    Reply
  9. insa

    I managed to correct the shutdown when disconnecting from ssh by launching it from another way.
    Now the question is : does the service has a limited execution time before going down ? Are you aware of any parameters to do the trick ?
    Thank for your time.

    Reply
  10. insa

    Hi, Thanks for that tutorial. It really helped me.
    Starting and Stopping the service work great.
    BUT when I disconnect myself from SSH, the service shuts downs too.
    How can I do to let it run and be stoppped only if I use the stop command ?

    Reply
    1. GodLikeMouse Post author

      Hi insa, if you’re using the daemon call to run your $bin (or PHP) then that should already be forked over into a new process not attached to the current SSH tunnel. Logging out should not kill that. If you already are using daemon to run your PHP then you may need to step through your PHP and see what is attaching itself to the current user’s SSH.

      Reply
  11. Paiman

    My setting Daemon is :

    #!/bin/bash
    #
    # /etc/init.d/Daemon
    #
    # Starts the at daemon
    #
    # chkconfig: 345 95 5
    # description: Runs the demonstration daemon.
    # processname: Daemon

    # Source function library.
    . /etc/init.d/functions

    #startup values
    log=/var/log/Daemon.log

    #verify that the executable exists
    test -x /var/www/refill/daemon.php || exit 0RETVAL=0

    #
    # Set prog, proc and bin variables.
    #
    prog=”Daemon”
    proc=/var/lock/subsys/Daemon
    bin=/var/www/refill/daemon.php

    start() {
    # Check if Daemon is already running
    if [ ! -f $proc ]; then
    echo -n $”Starting $prog: ”
    daemon $bin –log=$log
    RETVAL=$?
    [ $RETVAL -eq 0 ] && touch $proc
    echo
    fi

    return $RETVAL
    }

    stop() {
    echo -n $”Stopping $prog: ”
    killproc $bin
    RETVAL=$?
    [ $RETVAL -eq 0 ] && rm -f $proc
    echo
    return $RETVAL
    }

    restart() {
    stop
    start
    }

    reload() {
    restart
    }

    status_at() {
    status $bin
    }

    case “$1” in
    start)

    when Im running any Error : Starting Daemon: ./Daemon: line 31: daemon: command not found. Can anybody Help Me..

    Reply
    1. GodLikeMouse Post author

      Hi Paiman, I think you may be running into a collision with the name Daemon. Give your Daemon another name, something that better reflects what the actual functionality of the service and change the prog, proc and bin variables.

      Reply
  12. Jan Galler

    Hey,

    I’m using your tutorial to make my php socket server running. And now… I’ve a question: I really need to get noticed if the service/daemon should shutdown. I’m using the PHP function ‘register_shutdown_function()’ to do this but that doesnt work if I’m just doing ‘service asdf stop’. So, it’s not required to shut the server down by the service command, but it’s required to shut it down a good way.
    So if I do the ‘exit();’ command in the php socket server, the script gets terminated. BUT then the service stucks. The service cant restarted because stopping fails. What should I do?

    In general I’m using exactly your code:

    #!/usr/bin/php

    <?php

    $log = '/var/log/Daemon.log';

    /**
    * Method for displaying the help and default variables.
    **/
    function displayUsage(){
    global $log;

    echo "n";
    echo "Process for demonstrating a PHP daemon.n";
    echo "n";
    echo "Usage:n";
    echo "tDaemon.php [options]n";
    echo "n";
    echo "toptions:n";
    echo "tt–help display this help messagen";
    echo "tt–log= The location of the log file (default ‘$log’)n”;
    echo “n”;
    }//end displayUsage()

    //configure command line arguments
    if($argc > 0){
    foreach($argv as $arg){
    $args = explode(‘=’,$arg);
    switch($args[0]){
    case ‘–help’:
    return displayUsage();
    case ‘–log’:
    $log = $args[1];
    break;
    }//end switch
    }//end foreach
    }//end if

    //fork the process to work in a daemonized environment
    file_put_contents($log, “Status: starting up.n”, FILE_APPEND);
    $pid = pcntl_fork();
    if($pid == -1){
    file_put_contents($log, “Error: could not daemonize process.n”, FILE_APPEND);
    return 1; //error
    }
    else if($pid){
    return 0; //success
    }
    else{

    include(“SocketServer.php”);

    while (true) {
    sleep(86400);
    }

    }//end if

    ?>

    Reply
    1. GodLikeMouse Post author

      You could install a zap function into the service which would basically kill off the pid file and shutdown, that would allow you to get around the restart issue you’re having. Or you can add to your service a way to monitor the actual process. You could watch the actual process and if it is dead send out a notification via email or something else.

      Reply
  13. GodLikeMouse Post author

    Hi Dave,

    Sounds like you might be missing the functions file for BASH. Perhaps your distribution doesn’t need it. Try commenting that line out and see what happens.

    Reply
  14. Dave

    I get this error:
    line 12: /etc/init.d/functions: No such file or directory

    when I run theh BASH. Everyting up to this seems to work..

    Any suggestions?

    Thanks

    Reply
  15. Tom M

    Thanks for the great work here… It helped me big time. I was wondering how to create a /var/run/myscript.pid file so that I can configure monit to monitor the process? Thanks again!

    Reply
  16. GodLikeMouse Post author

    Hi Gene, there are quite a few ways you can do this. You can have the deamon listen to a port and fire commands to it that way. You can also have the daemon listen for changes in a database or watch a flat file and respond to events. I’ve never run into a situation where I needed to call a method of a php daemon directly from another interface, usually I have the daemon responding to events or polling. Hope this helps.

    Reply
  17. Gene Ellis

    Question…Once a PHP Daemon is running, how do we pass variables and execute functions from an external process. For example, if my daemon were taking in a publishing steam for various users, and then a user deactivate their account, I would need to fire certain functions in the already running daemon. How would I get access to those functions in the daemon, from a web form?

    Reply
  18. GodLikeMouse Post author

    Hi roman, most daemons traditionally rely on configuration scripts to control execution. You can verify this by looking into the daemon scripts located in /etc/init.d/. The daemon command on linux actually does a fork and detaches the currently executing script/program from the terminal and allows it to run behind the scenes. You could write your own fork to do this, then control the PID, etc…but you’re basically re-inventing the wheel. You might want to have a look at the “daemon” command using man help, could save you quite a bit of time.

    Reply
  19. roman

    hm…
    I thought you would tell about forking.
    This way works of course.
    But I think it’s better to avoid changing init scripts.
    May be it would be a good idea to run the daemon with exec() function,
    Then check if exists PID file and if it does. Kill itself if doesnt run its jobs.

    More interesting if the daemon can fork childs.

    I have problems with it

    Reply
  20. GodLikeMouse Post author

    One thing you can do is use the getmypid() PHP function to return the current PID. You could kick this value out to a file and read it as input for Monit. As far as I know you can’t actually assign a PID, these are generated by the Kernel at execution time. Hope this helps.

    Reply
  21. Gene Ellis

    Amazing. Thank you. This helped me solve some really important issues that were plaguing me. One quick question, how can we set a static PID value for the parent process? I want to use a monitoring system like Monit, however monit is based on monitoring PID values and I don’t see a PID file in the above example.

    Thank you for your help!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *