How To Run A PHP Script In The Background

If you have a PHP script that takes a while to execute (for example, long database operations or file format conversions), you might want to run it in the background so that the rest of your page still loads fast. In this post I’ll describe two ways to do this.

Launching a background process

One approach is to launch a new instance of the PHP interpreter as a background process. On a UNIX-based system this can be done with a single line of code :

exec ("/usr/bin/php path/to/script.php >/dev/null &");

To launch a background process in an OS-independent fashion you’d can use the function below (snatched from this post) -

function launchBackgroundProcess($call) {
     // Windows
    if(is_windows()){
        pclose(popen('start /b '.$call, 'r'));
    }
     // Some sort of UNIX
    else {
        pclose(popen($call.' > /dev/null &', 'r'));
    }
    return true;
}
 
function is_windows(){
    if(PHP_OS == 'WINNT' || PHP_OS == 'WIN32'){
        return true;
    }
    return false;
}

If you use one of these methods you can pass data to the PHP script using command line arguments.

The main disadvantage of this approach is that the command to execute ($call) is still platform-dependent. Also, the path to the PHP executable may be different on various servers.

Using an asynchronous HTTP request

If the PHP script that needs to be executed is available on the Web, you can easily run it in the background by requesting the URL and dropping the connection right away, so you don’t need to wait until it’s done processing. You can also pass information to the script in the usual way – as URL parameters.

Here’s a function that will submit an asynchronous POST query to the specified URL (this works similar to how a web form is submitted) -

function backgroundPost($url){
  $parts=parse_url($url);

  $fp = fsockopen($parts['host'], 
          isset($parts['port'])?$parts['port']:80, 
          $errno, $errstr, 30);
          
  if (!$fp) {
      return false;
  } else {
      $out = "POST ".$parts['path']." HTTP/1.1\r\n";
      $out.= "Host: ".$parts['host']."\r\n";
      $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
      $out.= "Content-Length: ".strlen($parts['query'])."\r\n";
      $out.= "Connection: Close\r\n\r\n";
      if (isset($parts['query'])) $out.= $parts['query'];
  
      fwrite($fp, $out);
      fclose($fp);
      return true;
  }
}

//Example of use
backgroundPost('http://example.com/slow.php?file='.
                       urlencode('some file.dat'));

Assuming that the target script is located on the same server as the calling script, this function is fast, plus it should work on any operating system.

By the way, I recommend to put a call to ignore_user_abort(true) right at the top of the backgroud script to make sure it’s not terminated when the connection is closed. Disabling the time limit with set_time_limit(0) can also be useful.

Need more?

If for some strange reason you can’t use the aforementioned tricks, you could also launch the script from the client side by using AJAX. This is what I do in my link checker plugin for WordPress. Another possibility is to store the tasks that need to be done in a database and periodically run the script using cron.

Related posts :

69 Responses to “How To Run A PHP Script In The Background”

  1. Thanks for this! I’ll check this site everyday and looking for some posts like this.

  2. Wow, fantastic weblog format! How lengthy have you ever been running a blog for? you made running a blog look easy. The full glance of your website is great, let alone the content material!

  3. Andrei says:

    Thank you for this information. Very useful!

    Question: for the following code, who is $errno and $errstr?
    $fp = fsockopen($parts['host'], isset($parts['port'])?$parts['port']:80, $errno, $errstr, 30);

  4. Jānis Elsts says:

    Those are the output parameters for the error number and error message. See the fsockopen documentation for (slightly) more details.

  5. Vivek says:

    Hi there…this is great! what edits would I need to make if I wanted to do this on an HTTPS request? On cURL I used to do SSL_VERFY_PEER set to false..anything I can do here?

  6. Jānis Elsts says:

    As far as I know, there’s no easy way to do HTTPS with plain sockets (fsockopen). You’ll probably want to change the script to use cURL instead. With an appropriately low connection timeout, cURL should close the connection right away – just like the script above does.

  7. Travis says:

    Hi Janis,
    is there a way to add a callback function/to be notified when the background process is completed? Thanks!

  8. Carl says:

    Thank you very much for this post.

    I have been searching for weeks for a solution like this and am so grateful for your work!

  9. Jānis Elsts says:

    @ Travis: If you use the first approach (the popen() function), you could modify the script to return the file pointer that it creates instead of closing it immediately, and then check its status by calling feof() on the pointer.

    Something like this:

    $fp = launchBackgroundProcess($command);
    //...other code...
    if (feof($fp)) {
        //The process has completed.
    }
    

    Note: One of the user comments on the popen() page claims that feof() will only ever return TRUE if you try to read from the file pointer at least once. I don’t know if this is true, but it’s something to keep in mind as a possible source of bugs.

  10. [...] the first. If you want a very good tutorial on how to do the above two methods, then check out this tutorial on w-shadow.com. It gives you code samples and explains quite a bit of [...]

  11. analytical says:

    Where could I input the url I want to load? Or what variable should I set, I want to use this with one variable at first, then from an array of urls.

  12. egiannopo says:

    Thanks for the useful trick.
    I have try some tests, and I found a strange situation.
    My Test procedure:
    3 scripts (xloop.php, yloop.php & zwrite.php)
    xloop.php calls 10 times (in a loop) the yloop.php script passing the x (1 to 10).
    yloop.php calls 10 times (in a loop) the zwrite.php script passign the x(that get from xloop) & y (1..10 & 1..10).
    z.php gets the 2 params (x & y) that saved in a log file.

    With simple maths, the zwrite script must run 100 times (10X10), and we expect to find 100 lines in our log file.
    But nop!!!

    Every time I run the xloop, I found less than 100 entries in the log (the lowest till now is 59)

    I have try also, to create a different log file (log-X-Y.txt) in case that the system or the disk can handle 100 IO in the same millisecond for the same file.
    But the results are similar. The log files in directory is < 100.

    Where is the problem? Some request are lost? The system can't handle to write 100 text files in the same millisecond?

    thoughts are welcome!!!
    Thanks in advance
    EG

  13. Jānis Elsts says:

    Huh, that’s interesting. Try also adding logging to the xloop.php and yloop.php scripts to see how many of the x & y loop iterations get executed on that level. It could help figure out what part of the the xloop.php -> yloop.php -> zwrite.php chain is causing the problem.

  14. egiannopo says:

    Hi Janis,

    I have try anything (even with mysql as log, or to add a delay between requests). I have upload the scripts (zip) at https://www.yousendit.com/download/M3Brek9qays3bURsZThUQw
    in case that anybody find something wrong in my code.

    ps. My case is, when I get a stock market value change, I have to do calculations and send an XML to different clients. So if I have 10 stock-market changes, at the same time (xloop) and 10 clients (yloop) do calc and send xml, for any pair for stock-client (zwrite). All these in 1 sec maximum.

    Thanks for your help
    EG

  15. Jānis Elsts says:

    I tried your script on my local server and the zwrite.php script was called exactly 100 times. However, I had to modify it to create a new log file for each $x/$y combination, as otherwise some runs would not be logged. Basically, I added this line to zwrite.php:

    file_put_contents(sprintf('logs/%2d-%2d.txt', $x, $y), $x. ', '. $y);
    

    All 100 files were successfully created in my “logs” directory.

    This seems to indicate that the script is not the problem, but rather that something is wrong with your server (e.g. it might be unable to handle so many requests in such a short time).

  16. Lakey says:

    Great script, was looking for something like this for a while, got a question though.

    With the HTTP request you can send information using URL parameters, but is it also possible to send information with a POST and how would one do that?

  17. Jānis Elsts says:

    The script does, in fact, use POST. URL parameters are used to pass arguments to the backgroundPost() function and are converted to POST fields before the HTTP request is sent.

    In retrospect, doing it that way may have been a bad design choice. It would be better to just pass the arguments as an associative array, then convert them to a query string with http_build_query().

  18. Rochelle says:

    Hi! Thank you for this! I’d been trying everything for hours and this is the only one that worked! :)

  19. Gaurang says:

    Hello,

    Thank you so much for this post. since 2 days, i was looking for this kind of thind and finally got from your post and it works for me……

    Excellent !

Leave a Reply