Peter Davies

Ramblings

Magento 2 cron and LiteMage

Two jobs that come up on every Magento 2 build I do: getting cron running properly, and putting a full-page cache in front of the store. On servers running LiteSpeed rather than Apache, the cache is LiteMage. This was Magento 2.2 in mid-2018.

Why cron matters on Magento 2

Magento 1 would limp along without cron. Magento 2 won't. Cron drives the indexers when they're set to "Update by Schedule", sends order and transactional emails from the queue, applies catalogue price rules, cleans up the cache, and generates sitemaps. A store without cron looks fine for a day or two and then prices are wrong, emails haven't gone out and the admin shows a "one or more indexers are invalid" banner.

The crontab

Cron has to run as the user that owns the Magento files. On a Virtualmin server that's the domain's user, not root and not www-data. If you run it as root, files it writes under var/ and generated/ end up owned by root and the site starts throwing permission errors.

crontab -u example -e

For Magento 2.1 and 2.2 there are three entries:

* * * * * /usr/bin/php /home/example/public_html/bin/magento cron:run | grep -v "Ran jobs by schedule" >> /home/example/public_html/var/log/magento.cron.log
* * * * * /usr/bin/php /home/example/public_html/update/cron.php >> /home/example/public_html/var/log/update.cron.log
* * * * * /usr/bin/php /home/example/public_html/bin/magento setup:cron:run >> /home/example/public_html/var/log/setup.cron.log

Use the same PHP binary the site uses. On a server with several PHP versions, /usr/bin/php may not be it; give the full path to the right one, for example /usr/bin/php7.1.

Magento 2.2 can write the entries for you:

cd /home/example/public_html
sudo -u example bin/magento cron:install
crontab -u example -l

It wraps its lines in #~ MAGENTO START and #~ MAGENTO END markers so it can find and replace them later. Leave the markers alone.

Checking it runs

Wait a couple of minutes and look at the schedule table:

SELECT job_code, status, scheduled_at, executed_at, finished_at
FROM cron_schedule
ORDER BY scheduled_at DESC
LIMIT 20;

You want a steady stream of success and pending rows. Lots of missed means cron isn't running every minute; rows stuck at running usually mean a job died part way, often on memory. The PHP CLI has its own php.ini, so check memory_limit there as well as in the web server's PHP.

LiteMage

LiteMage is LiteSpeed's full-page cache for Magento. It does the same job as Varnish but runs inside the LiteSpeed web server, and it uses edge side includes for the blocks that change per visitor (basket, account links), so pages can be cached for everyone and the personal parts filled in separately. It needs LiteSpeed Web Server Enterprise with a LiteMage licence; on plain Apache or OpenLiteSpeed there's nothing for the module to talk to.

Install the module with Composer, as the file owner, from the Magento root:

composer require litespeed/module-litemage
php bin/magento module:enable Litespeed_Litemage
php bin/magento setup:upgrade

setup:upgrade drops the store out of production mode's compiled state, so rebuild it:

php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy en_GB en_US
php bin/magento deploy:mode:set production --skip-compilation
php bin/magento indexer:reindex
php bin/magento cache:flush

List every locale the store uses in setup:static-content:deploy. Without them it only builds en_US, and a store view set to British English falls back to generating files on demand, which is slow and sometimes broken. setup:di:compile takes no locale arguments.

Switch LiteSpeed on for the site in the .htaccess in the Magento root:

<IfModule Litespeed>
    LiteMage on
</IfModule>

Then in the admin go to Stores, Configuration, Advanced, System, Full Page Cache and set Caching Application to LiteMage. Flush the cache once more.

Checking LiteMage

Request a category page twice and look at the headers:

curl -sI https://www.example.com/some-category.html | grep -i x-litespeed-cache

A hit on the second request means it's working. If you never get a hit, the usual causes are the licence not including LiteMage, the .htaccess lines missing, or something in the theme setting a cookie on every request.

The two halves are connected: when indexers run on schedule, it's cron that tells LiteMage which pages to purge after a price or stock change. If cron stops, the cache keeps serving the old prices.