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) -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.

Share :
  • Reddit
  • del.icio.us
  • Digg
  • StumbleUpon
  • DZone
  • Ping.fm
  • Sphinn
Related posts :

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

  1. 1
    Tuncay says:

    Hi there,

    I have a background task running in exact the same way you have in your example. Though the point is that I wan’t the script to popup a page or dialog or whatever when it’s done.
    1. So a visitor clicks and genarates a request which is being executed on the background.
    2. The visitor keeps surfing on different pages.
    3. No matter wich page he is, when the script is done he should see a popup or whatever that tells him/her that (in my case) the pdf is created and can be downloaded.

    What would be the best way to do this…is there a way to do this?

    Thank you!

  2. 2
    White Shadow says:

    I think the easiest solution in this case is to use AJAX. Assuming that …

    1. All requests are logged somewhere (e.g. a database).
    2. You know which request corresponds to which user (e.g. using a cookie or session variables).
    3. The background script can set some kind of “completed” flag for the request when it’s done, or the completeness can be verified in some other way.

    You can create a small PHP script that accepts the request’s ID (or somesuch) as a parameter, checks whether it’s done and outputs 1 if yes and 0 if no (just an example, you could use other values, JSON or even XML).

    Then add a small piece of JavaScript to every page that will query the aforementioned PHP script and notify the user if the processing is done. It could also display a new window, redirect to a page etc. I’d recommend putting this script in the footer of the page and only using it when there is a request being processed.

    If you’re not familiar with AJAX, here are some useful links –
    * What is AJAX? [Wikipedia]
    * The jQuery library (with some useful AJAX functions)
    * jQuery tutorials

  3. 3
    Tuncay says:

    Thank you White Shadow,

    That should be the best way indeed. The whole website is kinda ajax so, it should not be a problem to implement.

  4. 4
    Hi White Shadow says:

    Hi White Shadow

    Thanks for the script works v well and is the closest thing I’ve found so far to what I need (maybe i’m just not searching the right terminology).

    I basically need a similar script but where the php file is called locally using a relative path – i want to preserve session id and data (it is in a secure CMS) without having to have the user login again etc. Using your method it is treated as if another computer/person is accessing the file and php creates a new session id for the request.

    If you’ve got any advice would be greatly appreciated!

  5. 5
    Ross says:

    Its me again (my name was Hi White Shadow)

    Just on a side note… what would do/be good enough is if I could load the php page outside the webroot as this would provide enough security…

    Thanks !

  6. 6
    White Shadow says:

    Hmm… You could probably grab the cookies ($_COOKIE, I think) and use them with an asynchronous request (google it; sending cookies in a HTTP request), but I don’t know if it would work. The IP address of the script would be that of your server – not of the logged-in user, so the session id might be considered invalid by whatever CMS you’re using.

    AJAX would get around this, but might not be sufficiently secure/flexible for what you’re trying to do.

  7. 7
    php tutorial says:

    Hi,
    I have written a php fork using pcntl option, can I execute that page using this way?

  8. 8
    White Shadow says:

    I think that would probably work.

  9. 9
    Spiderman says:

    Hi,

    I was hoping I could use this to take advantage of the Private Message system that’s part of our vbulletin forum (to send responses from a custom program that handles special access requests)… but most of the parameters are expected as POST parameters, and I’m having difficulty figuring out exactly how to properly modify the header to include them. I’ve put them into a variable, and tried adding them to $out (before AND after $parts['query']), but no luck. Can you help?

    Thanks in advance!

  10. 10
    White Shadow says:

    Maybe I’m being a bit dense here, but I don’t get what, exactly, is your problem.

    I assume there are two possibilities :

    * If the form is normally submitted via POST, send the variables like this :

    backgroundPost('http://domain.com/path/formfile.php?variable1=value1&variable2=value2');

    ^ It would probably be a good idea to encode values with urlencode() before appending them to the URL.

    * If the form is submitted via GET you will need to modify the function, i.e. rename it to backgroundGet and make a few changes :

    Replace $out = “POST “.$parts['path'].” HTTP/1.1\r\n”; with

    $out = "GET $url HTTP/1.1\r\n";

    Leave the next line (setting the “Host”) unchanged, but comment out the four lines after that (ending with the line that begins with “if (isset(….)”).

    Then call the function the same way as in the first example.

  11. 11
    Spiderman says:

    Sorry, I guess I was confused by: the fact that the action=URL (in the form I am trying to mimick) has some parameters embedded in it (as if it were a GET); my lack of understanding of the parse_url function, and; my unfamiliarity with the syntax involved in creating headers (as your code was already creating POST headers). I was also missing a security-related parameter. I have a bare-bones version of the form working, but the same parameters, submitted as part of the URL to your function, do not produce the same results.

    Is there something that could be added to, or changed, in the function to still submit the POST query in the same manner, but show me the output/results so I could debug?

    Thanks for all your help

  12. 12
    White Shadow says:

    Maybe the “real” form expects a login cookie to be set or something like that? My script doesn’t support cookies.

    For debugging, I don’t have the time to test it, but inserting something like this before “fclose(…)” should work :

    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
  13. 13
    Spiderman says:

    Thanks for the debugging while/echo – it worked perfectly!

    I think you are correct (about the cookie). I’ve tried adding it, but I’m not sure if I have all the syntax right. I even tried cURL, thinknig that would be easier, but I ran into the same problem.

    Thanks again for all your help. I was really hoping this was going to be the answer for me. If you have time and are interested in looking at the code I’ve written, you can read the thread I started on my forum.

    Cheers.

  14. 14
    White Shadow says:

    Eh, I think I’ll just go and write the script/function myself, that will be simpler than trying to figure out and then explain (in great detail) how to do it ;) Should be done by tomorrow.

  15. 16
    Spiderman says:

    Thanks for the quick turn-around on this!

  16. 17
    Shafiul says:

    Its a nice script , great job .

  17. 18
    Arnie Shore says:

    I wanna do a PHP/JS/Ajax/Comet chat application, but not with the usual ‘check every n seconds’ logic. Simple environment: single server, possibly win32, possibly *nix. Gonna be Open Source. I’ve looked hard, nothing drops out, so here I am.

    Client-side, Comet (AKA xhr slow-load) looks like part of the solution.

    PHP’s sockets function look like candidate for the server-side user-to-user data exchanges. I’ve seen some daemons around, but they seem hard to test debug, so I’ve looked at yr threads as a possible solution. Have you done anything like this I might peek at? Or, … ? Thanks a lot for a super/cool/awesome/useful site!

  18. 19
    White Shadow says:

    I haven’t done anything complex with these thread scripts, or written any chat servers. I also have to confess my ignorance about Comet (though the wiki page looks interesting).

    Unfortunately I probably can’t help you in this particular case.

  19. 20
    Steve says:

    I couldn’t get the HTTP request to work. I echo’d the output and get:

    POST /quote/quotePricing.php HTTP/1.1 Host: dev.quote2bill.com Content-Type: application/x-www-form-urlencoded Content-Length: 7 Connection: Close quote=1

    … but the /quote/quotePricing.php script on dev.quote2bill.com does not seem to run for me. All the called script does at this point is insert into my DB as a test and it works stand alone.

  20. 21
    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.

  21. 22
    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…

  22. 23
    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.

  23. 24
    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!

  24. 25
    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.

  25. 26
    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.

  26. 27
    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!

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

  28. 31
    vibha says:

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

  29. 32
    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.

  30. 33
    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);

Leave a Reply