3

I know there's questions about this but so far none has helped me solve my problem.

I have a PHP script whose job is to send scheduled e-mails. It does this by calling a web service, which I control, via cURL.

Run in the browser, it works fine. Run via CRON, the cURL response is empty. It never reaches the web service; I know this because I had the WS write a text file when it's contacted. It does if you access via browser, not via CRON.

I know CRON runs in a different environment and I'm not relying on any environmental vars e.g. $_SERVER. The path to require() isn't the problem, either, as it's successfully getting data out of that file to connect to the DB.

This question suggested adding the following cURL opts, which I've done, but no dice:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

Here's my PHP script:

//prep
define('WS_URL', 'http://mywebservicedomain.com/index.php/web_service');
require '../application/config/database.php';
$db = new mysqli($db['default']['hostname'], $db['default']['username'], $db['default']['password'], $db['default']['database']) or die('failed to connect to DB');

//get scheduled e-mails
$res = $db->query('SELECT * FROM _cron_emails WHERE send_when < NOW()');

//process each...
while ($email_arr = $res->fetch_assoc()) {

    //...get API connection info
    $api_accnt = $db->query($sql = 'SELECT id, secret FROM _api_accounts WHERE project = "'.$email_arr['project'].'"');
    $api_accnt = $api_accnt->fetch_assoc();

    //...recreate $_POST env
    $tmp = json_decode($email_arr['post_vars'], 1);
    $_POST = array();
    foreach($tmp as $key => $val) $_POST[$key] = !is_array($val) ? $val : implode(',', $val);

    //...call API to send e-mail
    $ch = curl_init($url = WS_URL.'/send_scheduled_email/'.$email_arr['email_id'].'/'.$email_arr['store_id'].'/'.$email_arr['item_id']);
    curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_SAFE_UPLOAD => true,
        CURLOPT_SSL_VERIFYHOST => 1, //<-- tried adding this
        CURLOPT_SSL_VERIFYPEER => 1, //<-- ditto
        CURLOPT_POSTFIELDS => array_merge($_POST, array(
            'api_key' => $api_accnt['id'],
            'api_secret' => $api_accnt['secret'],
        ))
    ));
    $resp = curl_exec($ch); //<-- empty when CRON

    //if e-mail was sent OK, or decider script said it was ineligible, delete it from the queue
    if (($resp == 'ok' || $resp == 'ineligible'))
        $db->query('DELETE FROM _cron_emails WHERE id = '.$email_arr['id'].' LIMIT 1');

}

Does anyone have any idea why it fails to connect to my web service via cURL only when run in CRON?

Here's the CRON job:

/usr/bin/php /home/desyn/public_html/cron/send_scheduled_emails.php
Mitya
  • 33,629
  • 9
  • 60
  • 107
  • check path and SSL – Deep Kakkar Nov 27 '17 at 12:49
  • Can you be a little more specific? The path to the CRON script? It's executing, like I say, and the path to the `require()` is also executing. – Mitya Nov 27 '17 at 12:50
  • anything in your (error) logs? and what's the actual cron syntax you used? – Funk Forty Niner Nov 27 '17 at 12:53
  • Are you running exactly the same script from the browser or another version where you post the variables to the script? – jeroen Nov 27 '17 at 12:53
  • @Fred-ii- - no errors being reported in the e-mail I get for the job. As for the CRON syntax, that's in my question. It's a job declared in cPanel. – Mitya Nov 27 '17 at 12:54
  • @jeroen - same script, same variables. Nothing environmental going on. – Mitya Nov 27 '17 at 12:54
  • `/usr/bin/php /home/desyn/public_html/cron/send_scheduled_emails.php` is all you posted. The linked questions contains `*/5 * * * * /usr/bin/php /home/username/scripts/test.php` - unless you think it's irrelevant. – Funk Forty Niner Nov 27 '17 at 12:55
  • Sorry, I didn't realise you meant the time commands as well. It's `57 * * * *`, where `57` is updated constantly while I keep testing it. The job does run and I receive the e-mails as proof. – Mitya Nov 27 '17 at 12:57
  • 2
    Sometimes PHP configuration files are different for CLI and web; I'd suggest you run your cron command from bash and work from there. – Patrice Levesque Nov 27 '17 at 12:57
  • @PatriceLevesque - thanks, but I have no idea what that involves. Are you able to point me in the right direction? Is this a solution, or a means to test further? – Mitya Nov 27 '17 at 12:58
  • Do you have ssh access to the server? And what is the exact response from curl, `false`, an empty string, etc.? – jeroen Nov 27 '17 at 13:00
  • It's a means to test further. If you can SSH into the server and run your command from there, you may receive warnings or error messages that'll help you figure out where the problem is. – Patrice Levesque Nov 27 '17 at 13:01
  • 2
    A possible workaround could be to wget/curl your public facing url as a cron job to ensure the same environment. – Progrock Nov 27 '17 at 13:05
  • OK I've done some digging and added a call to `curl_error()` and `curl_getinfo()` to the script (should have done this to start with) and I'm getting the error "cURL error: Failed to connect to xxx.com port 80: Connection timed out". There's also a HTTP response code of 0. I have *disabled* the `CURLOPT_SSL_VERIFYHOST` and `CURLOPT_SSL_VERIFYPEER` options as, having read about them, it doesn't seem like activating them would help (since there's no SSL in play.) Oh and @jeroen, the response is an empty string, presumably due to this error. – Mitya Nov 27 '17 at 13:14
  • @Progrock - interesting; how would I do that? – Mitya Nov 27 '17 at 13:29
  • @Utkanos, something like: https://serverfault.com/questions/299287/how-do-i-use-curl-in-a-cron-job – Progrock Nov 27 '17 at 15:07
  • Thanks for all the help, guys. I eventually found the problem and have detailed it in my answer below. Thanks again. – Mitya Nov 28 '17 at 10:30

1 Answers1

0

In the end this turned out to be a very particular, and I imagine uncommon, problem. I doubt it'll prove too much help to others, but I leave it here in case.

Essentially, my server didn't like cURL'ing itself, via a domain.

The server being called by cURL in the CRON script was the same server that the CRON task was running on (but a different account and website.)

This has not proved to be a problem via a browser; only via CRON.

Changing the cURL request to use the server's local IP, rather than using the domain, solved this.

Mitya
  • 33,629
  • 9
  • 60
  • 107