The title pretty much sums it up. Is there a difference between the shell file and php file for the Magento cron?

If there is a difference is there a reason to run one instead of the other?

  • You better use cron.sh, it will check if there is any cron.php process running before call it. – Tai Christian Oct 1 '15 at 7:57

The cron.sh is pointing to the cron.php file, so you should be pointing your cronjob task to the .sh file.

Basically inside the PHP file resides all the logic for the cron to retrieve jobs from Magento and the sh file invokes the PHP file.

  • 4
    The cron.sh file is set up to check that there isn't a cron process running in Magento before starting a new one. Always use it as the trigger. Under certain security schemes with WHM/cPanel, you may not be allowed to run shell scripts as cron jobs and only then do you directly run cron.php from crontab. – Fiasco Labs Nov 28 '14 at 17:19
  • To add to @FiascoLabs you may have shell_exec disabled in WHM/cPanel, but that doesn't mean it is reported as disabled when cron.php checks ini_get('disable_functions'). So cron tries to run, sees shell_exec as not disabled, tries to use it and fails because it is disabled. shrug – pspahn Jan 5 '15 at 20:49

You should be using cron.sh, i.e.

* * * * * /bin/sh /var/www/html/magento/cron.sh

Depending on your environment, cron.sh runs cron.php which runs cron.sh which runs cron.php. It's designed to prevent Magento's cron from executing jobs multiple times, or spawning too many processes that overlap.

The first time it's run, cron.sh will check the current running processes to see if cron.php is already running (without any arguments). If not, it will execute

/usr/bin/php /var/www/html/magento/cron.php &

On cron.phps first run (and depending if your OS/host supports it) it will spawn cron.sh again, twice, but this time passing arguments:

/bin/sh /var/www/html/magento/cron.sh cron.php -mdefault 1 > /dev/null 2>&1 &
/bin/sh /var/www/html/magento/cron.sh cron.php -malways 1 > /dev/null 2>&1 &

Back in cron.sh for the second time, it will check again to see if cron is running with the specified params. If not, it will pass it back to cron.php with either default or always.

/usr/bin/php /var/www/html/magento/cron.php -mdefault &
/usr/bin/php /var/www/html/magento/cron.php -malways &

And in cron.php for the last time, it will trigger Magento to run default cron jobs (pretty much all of them), as well as always cron jobs (like enterprise_refresh_index). By separating them into two processes it reduces the risk of a long running job from blocking others.



Use /bin/sh to process this script


Set a constant CRONSCRIPT with the file to call. $1 is the first argument, like cron.sh /whatever/path/cron.php

# location of the php binary
if [ ! "$1" = "" ] ; then

set another constant, here you can pass always or default explicitly.

if [ ! "$2" = "" ] ; then
    MODE=" $2"

cron doesn't have any environment variables, therefore you can't just call php. which tells you, where the php binary is living, most likely in /bin/php

PHP_BIN=`which php`

$0 is the file itself, like __FILE__ in php

# absolute path to magento installation
INSTALLDIR=`echo $0 | sed 's/cron\.sh//g'`

Not sure exactly how this works, but what it does: call cron.php with php.

#   prepend the intallation path if not given an absolute path
if [ "$INSTALLDIR" != "" -a "`expr index $CRONSCRIPT /`" != "1" ];then
    if ! ps auxwww | grep "$INSTALLDIR$CRONSCRIPT$MODE" | grep -v grep 1>/dev/null 2>/dev/null ; then
    if  ! ps auxwww | grep "$CRONSCRIPT$MODE" | grep -v grep | grep -v cron.sh 1>/dev/null 2>/dev/null ; then


As already said, cron doesn't have a working dir or any other environment variable, therefore the working dir is set.

// Change current directory to the directory of current script

require 'app/Mage.php';

if (!Mage::isInstalled()) {
    echo "Application is not installed yet, please complete install wizard first.";

If you call cron.php by curl or something, the filenames are fixed?

// Only for urls
// Don't remove this
$_SERVER['SCRIPT_NAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_NAME']);
$_SERVER['SCRIPT_FILENAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_FILENAME']);


Set umask, which defines with what permissions are new files created - zero permissions, no one is allowed to do anything.


Make sure, all functions are allowed which are needed.

$disabledFuncs = explode(',', ini_get('disable_functions'));
$isShellDisabled = is_array($disabledFuncs) ? in_array('shell_exec', $disabledFuncs) : true;
$isShellDisabled = (stripos(PHP_OS, 'win') === false) ? $isShellDisabled : true;

set $cronmode

try {
    if (stripos(PHP_OS, 'win') === false) {
        $options = getopt('m::');
        if (isset($options['m'])) {
            if ($options['m'] == 'always') {
                $cronMode = 'always';
            } elseif ($options['m'] == 'default') {
                $cronMode = 'default';
            } else {
                Mage::throwException('Unrecognized cron mode was defined');
        } else if (!$isShellDisabled) {

if cronmode is not set, we call cron.sh with both modes

            $fileName = basename(__FILE__);
            $baseDir = dirname(__FILE__);
            shell_exec("/bin/sh $baseDir/cron.sh $fileName -mdefault 1 > /dev/null 2>&1 &");
            shell_exec("/bin/sh $baseDir/cron.sh $fileName -malways 1 > /dev/null 2>&1 &");

And then magento is finally doing its job:

  • load event observers and add them to the observer pool


if shell_exec is disabled, dispatch events, so \Aoe_Scheduler_Model_Observer::dispatchAlways and \Mage_Cron_Model_Observer::dispatch are running the cron tasks.

    if ($isShellDisabled) {
    } else {
} catch (Exception $e) {

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.