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
cron:runis the main one. It reads the schedule from every module'scrontab.xmland runs whatever is due. Thegrep -vdrops the "Ran jobs by schedule" line it prints on every run, so the log only shows something when there's something to see.update/cron.phpandsetup:cron:runserve the Web Setup Wizard and Component Manager in the admin. If you only ever upgrade through Composer on the command line, they do nothing useful, but the wizard's readiness check fails without them.
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.