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 :

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

  1. White Shadow says:

    Test it with HTTP/1.0 instead of HTTP/1.1 and see what happens. Also, comment out the “Content-Type” and “Content-Length” lines if you’re not sending any parameters to the background script. Using these headers when there isn’t actually any data in the POST might confuse some servers.

  2. Steve says:

    Thanks for the ideas, but no luck for me. The request looks like:

    POST /quote/quotePricing.php HTTP/1.0 Host: dev.quote2bill.com Connection: Close

    At this point, I’m thinking of just using “one way” AJAX to kick off the script…

  3. White Shadow says:

    Another idea : you could use the code fragment in comment #12 to see what response you get back to the HTTP request, headers and all.

    The AJAX method seems to work OK in most cases, but sometimes it can cause inexplicable slowdowns in certain browser configurations.

  4. Steve says:

    I got the AJAX approach to work in about 5 minutes after struggling with exec(), system() etc for a couple of days, so I think I’ll just cut my losses 🙂 I’ll keep your idea about debugging in mind in case I run in to issues with AJAX. Thanks!

  5. Steve says:

    OK, I was curious, so I tried one more time with the debug you mentioned and got:

    Bad Request
    Your browser sent a request that this server could not understand.
    Reason: You’re speaking plain HTTP to an SSL-enabled server port.
    Instead use the HTTPS scheme to access this URL, please.

    Hint: https://dev.quote2bill.com/

    Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.

    I suspected it had something to do with that and had already changed the port number. Any idea what else needs to be changed in the request? Thanks.

  6. Arnie Shore says:

    There’s a potential skunk at the ajax picnic that I, for one, occasionally forget – it’s the two-channels limit thing. (If some of you have tried to test-drive this issue, pls let us know – I haven’t taken the time.)

    When it rears its ugly head it’s simply a hang – or unexpected delay – which can be really difficult to trace. It bears thinking about while building an application.

    I’d asked on the GMaps API group re how Google seems to solve that, and the answer I got was that the limit is per-server; you may have noticed that Google employs several server addr’s (MT1, MT2, etc.) to help solve it. Which makes sense but which not all of us can do.

  7. Steve says:

    Yes Arnie, that is worrying me as well, so I tried something novel and actually read the fsockopen documentation (http://us2.php.net/fsockopen). The comment from “joe at edwardsconsultants dot com 10-Aug-2003 09:56” clued me into prepending the host with “ssl://” in the fsockopen call and now my code is happy (as am I). So, for HTTPS, prepend the host and use port 443 (if you get stuck, use comment #12 debug as suggested):

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

    $fp = fsockopen(‘ssl://’.$parts[‘host’],
    isset($parts[‘port’])?$parts[‘port’]:443,
    $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;
    }
    }

    Thanks to Whiteshadow for a great idea and all of the follow up!

  8. […] more here: ø How To Run A PHP Script In The Background | W-Shadow.com ø Share and […]

  9. vibha says:

    Thnx….it works !!!!!!!!!!!!

  10. xendex says:

    Thanks for the exelent solution, White Shadow.
    I have a question about 2nd solution: how can I pass additional arguments via $_POST method(not GET like http://example.com/slow.php?file=some_file.dat) to the requested php-file? Need some example, please.

  11. White Shadow says:

    It already passes any parameters via POST, though it may not be immediately obvious. What it does is take the query string (e.g. “file=some_file.dat” in the example) and sends it as the body of the POST request. This means the requested file will see it as $_POST[‘file’].

    This may not be an optimal solution, but it should be pretty easy to change if you need to – change the function to take another arguments (e.g. $params, an array) and replace line 16 with something like

    $out .= http_build_query($params);
  12. Hamad says:

    Thank you man I’ve been looking for this for ages

    I’m working on a web crawler script that takes a long time to finish, which causes the connection to timeout.
    Thanks to you I can do it now without the user thinking the website has faild.

    I still can get the progress of the process by using a shared sessions variable and ajax to pull the current progress results.

  13. ShavedApe says:

    Just wanted to say a big thank you been looking to do this for so long but couldnt find a way other than exec() and I could never get that to work the way I wanted the backgroundPost() has helped me massively so thank you 😀

  14. DJ says:

    The backgroundPost() function works nicely to send a PHP script in the background. Now, I’d like to use it 3 times and expected that the 3 scripts will be running at the same time, but what happens is that one start after the previous one finishes.
    for example, my main script has:
    backgroundPost(‘http://’.$server.’script1.php?sid=’.htmlspecialchars($file_id));
    backgroundPost(‘http://’.$server.’script2.php?sid=’.htmlspecialchars($file_id));
    backgroundPost(‘http://’.$server.’script3.php?sid=’.htmlspecialchars($file_id));

    script1 has to finish (in the background) before I see script2 running, and so forth. Is this the expected behavior or is there something I am missing?

    Thanks again for posting this function!

  15. White Shadow says:

    That’s not the expected behaviour. Assuming network latency is negligible, all three scripts should start at almost the same time.

    How are you measuring when scripts start/finish? Also, is there anything in the scripts that could block one script’s execution until the previous one has finished (e.g. all scripts writing to the same file)?

  16. DJ says:

    Thanks for getting back to me that fast!
    The 3 scripts do not write to the same file, but that made me think. They do hit the same web service, and because of its restrictions of use, it probably explains the behavior I see.
    The only measure I got for knowing when the scripts start is the writing of a file: each script start by creating a (different) file.

    Thanks again, I really appreciate the function and the support!!

    DJ

  17. I have tried your script in following two pages:

    Basically what i doing is I m trying to send some value via GET method & want to retrieve it in Background process page ie. test2.php but i m not getting that variable!!!

    Plz check what i m doing wrong!!!

    —————————————————————————-

    Page 1 – test1.php

    —————————————————————————–

    Page 2 – test2.php

  18. Jeff says:

    Wonderful post, thanks helped a million. The fsockopen method works brilliantly.

    Regards,

  19. Scotty B says:

    The asynchronous HTTP request was just what I needed. Thanks!

Leave a Reply