Встроенный WP-Cron в WordPress срабатывает не по таймеру системы, а при заходах на сайт: кто-то открыл страницу, и WordPress заодно проверяет очередь задач. Как сказано в справочнике для разработчиков плагинов, WP-Cron не работает постоянно, и для задач, которые должны выполняться вовремя, это проблема. Решение там же: отключить запуск на каждой странице и повесить вызов на системный планировщик. Ниже как это сделать на Linux через crontab.
Чем плох WP-Cron из коробки
- нет посещений, нет запуска: отложенные публикации, рассылки и бэкапы плагинов опаздывают;
- на каждой загрузке страницы WordPress проверяет очередь, и это лишняя нагрузка на занятом сайте;
- нельзя гарантировать интервал «каждые N минут» независимо от посещаемости.
Системный cron вызывает задачи сам по расписанию и от посетителей не зависит.

Шаг 1. Отключить встроенный запуск
В wp-config.php до строки /* That's all, stop editing! */ и подключения wp-settings.php добавьте:
define( 'DISABLE_WP_CRON', true );
После этого WordPress перестанет запускать задачи при открытии страниц. Сама очередь никуда не денется, её теперь нужно вызывать снаружи. В документации по wp-config.php рядом описана ещё одна константа, WP_CRON_LOCK_TIMEOUT: она не даёт cron запускаться чаще, чем раз в указанное число секунд, по умолчанию 60.
Шаг 2. Задание в crontab
Откройте crontab пользователя, от которого работает сайт, например www-data, или своего, если файлы сайта принадлежат вам:
sudo crontab -u www-data -e # или для текущего пользователя crontab -e
Есть три способа вызвать очередь. Первый, самый надёжный, через WP-CLI: он запускает задачи прямо в PHP, без веб-сервера и кэша.
*/5 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now --quiet
По документации WP-CLI, ключ --due-now запускает все задачи, у которых подошло время, и учитывает блокировку doing_cron, поэтому запуски не наложатся друг на друга. Для мультисайта есть ключ --network, он проходит по всем сайтам сети.

wp cron event run есть всё нужное для системного cron: --due-now для задач, у которых подошло время, и --network для мультисайта. Источник: developer.wordpress.org, wp cron event run.Второй способ через HTTP, его и показывает справочник WordPress. Подставьте свой домен:
*/5 * * * * wget -q --delete-after "https://example.com/wp-cron.php?doing_wp_cron" >/dev/null 2>&1 # или curl */5 * * * * curl -fsS -o /dev/null "https://example.com/wp-cron.php?doing_wp_cron" >/dev/null 2>&1
Ключ --delete-after нужен, чтобы wget не складывал ответ сервера в файл при каждом запуске. Этот вариант работает где угодно, но зависит от веб-сервера, файрвола и защиты от ботов: если запрос к wp-cron.php режется, задачи не выполнятся.

--delete-after. Источник: developer.wordpress.org, Hooking WP-Cron Into the System Task Scheduler.Третий способ через PHP CLI, если WP-CLI нет:
*/5 * * * * cd /var/www/html && /usr/bin/php wp-cron.php >/dev/null 2>&1
Путь к PHP, WP-CLI и корню сайта замените на свои: which php, which wp и ваш document root.
Интервалы в crontab
Формат строки: минута, час, день месяца, месяц, день недели, команда.
*/5 * * * *каждые 5 минут, обычный выбор для WordPress;0 * * * *каждый час в :00;0 3 * * *каждый день в 03:00;0 3 * * 1каждый понедельник в 03:00.
Большинству сайтов хватает запуска раз в 5-15 минут. Чаще имеет смысл, только если есть задачи, критичные по времени, и сервер это выдерживает.
Проверка
crontab -l # список заданий wp cron event list # очередь задач и время следующего запуска journalctl -u cron -n 50 # логи cron в Debian и Ubuntu
Если WP-CLI нет, очередь удобно смотреть плагином WP Crontrol: после системного вызова задачи с подошедшим временем должны уходить из списка. Для отладки можно временно писать вывод в файл:

*/5 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now >> /tmp/wp-cron.log 2>&1
Изменить или удалить задание
Снова crontab -e: поправьте строку или удалите её и сохраните файл. Команда crontab -r удаляет все задания пользователя сразу, с ней осторожно.
Плюсы и риски
- Плюсы: стабильный интервал без посетителей, никаких сюрпризов на тихих сайтах, нет лишней проверки очереди на каждой странице.
- Риски: нужен доступ к серверу; опечатка в пути или адресе, и cron молча ничего не делает; слишком частый запуск тяжёлых задач грузит процессор и базу; на shared-хостинге crontab может быть ограничен панелью.
На обычном shared-хостинге без доступа к cron иногда оставляют WP-Cron как есть или используют планировщик в панели хостера. На VPS связка DISABLE_WP_CRON и системного cron это нормальная практика.
Если хочется, чтобы на сайте это настроили вместе с обновлениями и бэкапами, по прайс-листу доработки существующего сайта стоят от 5 000 ₽, техническая поддержка от 20 000 ₽ в месяц.
В итоге замена занимает пять минут: строка define( 'DISABLE_WP_CRON', true ); в wp-config.php, задание в crontab раз в 5 минут через wp cron event run --due-now или wget, и проверка через wp cron event list. После этого задачи WordPress выполняются по расписанию, а не тогда, когда на сайт кто-то зашёл.

