WordPress выполняет свои фоновые задачи — отложенную публикацию, рассылку, очистку, прогрев кэша — «попутно»: при обычном запросе страницы он проверяет, не пора ли что-то запустить. На сайте, где страницы отдаются из кэша nginx, этот механизм почти перестаёт работать: запрос страницы до PHP больше не доходит.
Это вторая часть цикла «WordPress на кэшируемом сайте».
Почему встроенный крон не годится
- Запросов к PHP мало и они неравномерны. Страница из кэша PHP не трогает. До WordPress доходят только промахи кэша, ajax-запросы и работа в админке. Ночью может не быть ни одного — и отложенная на 3:00 статья выйдет утром.
- Запуск идёт запросом сайта к самому себе. WordPress стучится на свой же адрес
/wp-cron.php. В контейнере Docker запрос на собственный домен у нас не проходит вовсе (cURL error 7): адрес снаружи, а обратно в ту же машину маршрута нет. Задачи просто не стартуют, и никаких ошибок на экране. - Длинные задачи режет лок. Встроенный замок крона живёт 60 секунд (
WP_CRON_LOCK_TIMEOUT). Задача дольше минуты рискует запуститься второй раз параллельно себе — для прогрева кэша или рассылки нужен свой замок.
Как правильно: системный крон раз в минуту
- выключить запуск «попутно» в
wp-config.php; - запускать
wp-cron.phpвнешним планировщиком раз в минуту — от того же пользователя, под которым работает PHP; - проверить по журналу планировщика, что задание идёт и завершается без ошибок.
// wp-config.php
define('DISABLE_WP_CRON', true);
У нас планировщик — отдельный контейнер, который читает метки контейнеров сайта. Задание описывается метками в compose-файле сайта, примерно так:
labels:
- "ofelia.enabled=true"
- "ofelia.job-exec.site-wpcron.schedule=@every 1m"
- "ofelia.job-exec.site-wpcron.command=php -f /var/www/wp-cron.php"
- "ofelia.job-exec.site-wpcron.user=www-data"
С обычным crontab на сервере это одна строка: раз в минуту php -f wp-cron.php от пользователя веб-сервера.
Грабля 1: крон от root
Если задание запускается от root, всё, что оно создаёт — миниатюры, файлы кэша, логи, — принадлежит root. Потом PHP под своим пользователем не может эти файлы перезаписать: Permission denied, картинка не генерируется, кэш не обновляется.
Масштаб, который мы видели: на одном сайте за час набежало 3811 файлов и каталог от root и 90 ошибок Permission denied; на соседнем — 140 523 файла кэша миниатюр от root, накопленные за долгое время незаметно.
Планировщик читал метки только при своём старте. Контейнер сайта пересоздали с меткой «запускать от www-data», а крон ещё пять часов ходил старой командой от root, пока не перезапустили сам планировщик. После любой правки задания — перезапуск планировщика и проверка по его журналу.
И обратная ошибка: дописать в команду runuser -u www-data -- «для надёжности». Если планировщик уже запускает задание от www-data, runuser под обычным пользователем падает за 92 мс, и задание не выполняется вовсе.
Проверка, а не вера на слово:
# кто запускает крон
ps -eo user,args | grep wp-cron
# сколько файлов кэша не у веб-пользователя (должно быть 0)
find wp-content/cache ! -user www-data | wc -l
Грабля 2: зонды будят крон
Любой служебный скрипт, который подключает wp-load.php, — проверка, миграция, замер — сам запускает задачи крона, если «попутный» запуск не выключен. Замер, сделанный «посмотреть очередь», выполнит эту очередь. Поэтому в каждом таком скрипте первой строкой:
define('DISABLE_WP_CRON', true);
require '/var/www/wp-load.php';
Что ещё стоит знать про одноразовые события
wp_schedule_single_event молча не ставит событие, если такое же уже назначено в пределах 10 минут, — подробно в 36-й части цикла про кэш страниц. А очередь, которая перепланирует себя только при успешном прогоне, однажды встаёт навсегда — 35-я часть.
Отложенные статьи выходят не вовремя, а задачи «иногда не срабатывают»?
Пришлите адрес — проверю, кто и как запускает крон WordPress на вашем сервере.
// загружаю комментарии…