Как отключить wp-cron и настроить системный cron
WP-Cron запускается только при посещении сайта, что вызывает задержки. Настройте выполнение задач через системный cron и WP-CLI для стабильной работы планировщика WordPress.
Что такое wp-cron и почему системный cron его заменяет
WP-Cron — это встроенный в WordPress планировщик задач, который запускается только при обращении пользователя к странице. Внутри WordPress ни один процесс не запускается самостоятельно. При каждом запросе, который не был обслужен из кэша, WordPress считывает список запланированных задач. Если срок выполнения наступил, он отправляет повторный HTTP-запрос самому себе по адресу /wp-cron.php для выполнения работы. Перенос этой задачи в системный cron обеспечивает предсказуемый запуск по фиксированному расписанию, независимо от того, было ли на сайте тысяча посетителей в эту минуту или ни одного.
Фактическую работу выполняют две строки: константа в wp-config.php и запись в crontab. Всё остальное в этом руководстве — это детали, которые не охватывают эти две строки. От имени какого пользователя должна выполняться задача, как подтвердить, что запланированные события действительно сработали, и три способа, которыми эта настройка может выйти из строя без вывода сообщений на сайте.
В примерах используются /srv/www/example.com в качестве директории WordPress и www-data в качестве пользователя веб-сервера. Везде подставляйте свои пути и имя пользователя.
Какой посетитель инициирует выполнение cron на нагруженном сайте
Каждый некэшированный запрос оплачивает проверку. WordPress загружает опцию cron, сравнивает метки времени, и если пришло время выполнения, вызывает spawn_cron(), который отправляет неблокирующий loopback-запрос к /wp-cron.php. Посетитель не ждет результата. Его ждет PHP-воркер. На небольшом VPS с PHP-FPM и pm.max_children = 5 одно медленное запланированное задание занимает пятую часть ваших PHP-ресурсов на все время выполнения, и вероятнее всего, оно будет запущено в самую нагруженную минуту, так как именно в этот момент происходит больше всего загрузок страниц.
WordPress ограничивает дубликаты. Он устанавливает блокировку, время жизни которой составляет WP_CRON_LOCK_TIMEOUT, по умолчанию 60 секунд, поэтому одновременные посетители не запускают процесс каждый по отдельности. Блокировка ограничивает дублирование. Она не выносит выполнение задачи за пределы пути запроса.
Подсчитайте, как часто он срабатывает на вашем сервере, прежде чем решать, имеет ли это значение. Каждый loopback-запрос отображается в access log веб-сервера:
sudo grep -c 'wp-cron.php' /var/log/nginx/access.logApache вместо этого пишет в /var/log/apache2/access.log. Тысячи срабатываний в день — это реальные затраты, и это то число, которое следует измерить на собственной машине, а не читать в статье, точно так же, как вы бы сделали бенчмарк VPS до и после любого другого изменения.
Кэширование меняет картину. Если кэш страниц обслуживает большинство запросов как статический HTML, PHP для этих запросов не запускается, поэтому проверка cron никогда не происходит. Нагруженный сайт с активным кэшированием начинает вести себя как малонагруженный сайт, описанный ниже.
Почему посещения сайта влияют на работу cron
Отсутствие посетителей означает отсутствие выполнения cron. Сайт, который получает лишь несколько посещений в день, запускает запланированные задачи столько же раз, в случайные моменты времени, когда приходят эти посетители.
Симптомы всегда выглядят одинаково. Запись, запланированная на 09:00, остается в списке записей со статусом Missed schedule до тех пор, пока кто-нибудь не загрузит страницу. Плагины для резервного копирования пропускают ночные задания. Проверки обновлений задерживаются, поэтому на панели управления не отображаются доступные обновления, даже если уже вышел патч безопасности. Письма о заказах, уведомления о продлении и предупреждения об истечении срока действия отправляются с опозданием.
Ни одно из этих событий не фиксируется в логах как ошибка. С точки зрения WordPress задача не была просрочена, так как она попросту не была запущена.
Шаг 1: отключение триггера посетителей в wp-config.php
Откройте /srv/www/example.com/wp-config.php и добавьте константу:
define( 'DISABLE_WP_CRON', true );Разместите её перед строкой, содержащей /* That's all, stop editing! Happy publishing. */, так как строка сразу после этого комментария требует wp-settings.php, а wp-settings.php — это место, где WordPress привязывает проверку cron к init. Константа, определённая после этого require, устанавливается слишком поздно, чтобы на что-то повлиять: файл выглядит корректно, но триггер продолжает работать.
Убедитесь, что строка находится там, где вы предполагаете:
grep -n "DISABLE_WP_CRON\|stop editing" /srv/www/example.com/wp-config.phpЭта константа не останавливает планирование событий. Плагины продолжают добавлять задачи в очередь, как и раньше. Она лишь запрещает загрузчикам страниц запускать эту очередь, что означает, что очередь теперь не будет выполняться до завершения шага 3.
Она также не блокирует прямые запросы к /wp-cron.php. Любой пользователь по-прежнему может запросить этот URL, что обычно безопасно, так как файл выполняет только те задачи, срок которых наступил. Блокировка этого файла в конфигурации веб-сервера является опциональной. Если вы всё же заблокируете его, резервный механизм curl, описанный в конце этого руководства, также перестанет работать.
Шаг 2: установка WP-CLI
WP-CLI — это официальный инструмент командной строки для WordPress. Ему требуется бинарный файл PHP для командной строки, который поставляется как отдельный пакет, отличный от модуля PHP для веб-сервера.
php -v
sudo apt install -y php-cliУстановите WP-CLI из phar-сборки, как это рекомендуется в официальном руководстве по установке:
cd /tmp
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --infophp wp-cli.phar --info выводит путь к бинарному файлу PHP, версию PHP и версию WP-CLI. Если отображаются все три параметра, значит, phar работает корректно. По состоянию на октябрь 2026 года в руководстве по установке указана PHP 7.2.24 как минимально необходимая версия, а Ubuntu 24.04 поставляется с PHP 8.3, поэтому актуальный сервер полностью соответствует этим требованиям. Обновление выполняется позже с помощью sudo wp cli update.
Запускайте WP-CLI от имени пользователя сайта, никогда не используйте root:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com core versionОт имени root WP-CLI отказывается запускаться:
Error: YIKES! It looks like you're running this as root.Он предлагает использовать --allow-root. Не используйте этот флаг здесь. Причина описана в первом сценарии сбоя ниже.
Также обратите внимание, что sudo -u www-data -i не работает, так как оболочкой входа для этой учетной записи является /usr/sbin/nologin, и вы получите This account is currently not available.. Передача команды напрямую в sudo -u позволяет пропустить оболочку входа, поэтому она выполняется успешно.
Теперь подтвердите, что сам WordPress видит константу из шага 1:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com eval 'var_dump( DISABLE_WP_CRON );'Эта команда выводит bool(true). Фатальная ошибка о неопределенной константе означает, что строка define() не считывается, что обычно происходит, если она размещена после команды require.
Шаг 3: добавление записи в cron от имени нужного пользователя
Правильный пользователь — это владелец файлов, в которые пишет PHP. Проверьте оба параметра:
stat -c '%U %G' /srv/www/example.com/wp-content/uploads
grep -E '^(user|group) = ' /etc/php/8.3/fpm/pool.d/www.confВ стандартной установке Ubuntu оба значения равны www-data. Если вы создали для сайта отдельный пул PHP-FPM с собственным пользователем, что обычно является финальным этапом настройки стека LAMP на Ubuntu 24.04, используйте этого пользователя для всех последующих действий.
Создайте каталог для логов, в который этот пользователь может записывать данные:
sudo install -d -o www-data -g www-data -m 750 /var/log/wp-cronОтредактируйте crontab этого пользователя:
sudo crontab -u www-data -eДобавьте одну строку:
*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1Разберем по частям. */5 запускает задачу каждые пять минут. flock -n использует файл блокировки и немедленно завершает работу, если предыдущий процесс еще выполняется. /usr/local/bin/wp — это абсолютный путь, который необходим для cron. --path позволяет команде выполняться из любой рабочей директории. --due-now запускает только те события, время которых подошло, вместо обработки всей очереди. Перенаправление вывода отправляет стандартный вывод и ошибки в один файл, который вы сможете прочитать.
На практике это перенаправление обязательно. Cron отправляет вывод задания на почту пользователя, но в большинстве образов VPS не установлен агент передачи почты (MTA), поэтому cron записывает (CRON) info (No MTA installed, discarding output) и удаляет вывод. Файл позволяет сохранить данные для анализа.
Проверьте, что файл был сохранен:
sudo crontab -u www-data -lДля нескольких сайтов добавьте по одной строке для каждого, разнеся их по времени, чтобы они не запускались одновременно:
*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-one.lock /usr/local/bin/wp --path=/srv/www/one.example.com cron event run --due-now >> /var/log/wp-cron/one.log 2>&1
2-59/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-two.lock /usr/local/bin/wp --path=/srv/www/two.example.com cron event run --due-now >> /var/log/wp-cron/two.log 2>&1Лог будет расти бесконечно, если его не ротировать. Создайте файл /etc/logrotate.d/wp-cron:
/var/log/wp-cron/*.log {
weekly
rotate 4
missingok
notifempty
compress
create 640 www-data www-data
}Проверьте корректность синтаксиса без внесения изменений: sudo logrotate --debug /etc/logrotate.d/wp-cron.
Шаг 4: подтверждение выполнения запланированных событий
Успешное сохранение строки в crontab еще ничего не гарантирует. Выполняйте проверку от самых простых методов к тем, которые дают окончательный ответ.
Во-первых, запустил ли cron вашу команду? Cron записывает логи в журнал под своим собственным юнитом:
journalctl -u cron.service --since "15 min ago" | grep wpКорректная запись выглядит следующим образом (временная метка и имя хоста в начале опущены):
CRON[24913]: (www-data) CMD (/usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1)Эта строка означает, что cron запустил вашу команду от имени www-data. Она не дает информации о том, была ли команда выполнена успешно.
Во-вторых, выполнил ли что-либо WordPress? Прочитайте файл лога:
sudo tail -n 20 /var/log/wp-cron/example.logWP-CLI выводит по одной строке на каждое событие, а затем итоговую информацию:
Executed the cron event 'wp_version_check' in 0.418s.
Success: Executed a total of 2 cron events.Ошибки попадают в тот же файл, в этом и заключается основная цель 2>&1. В большинстве случаев, если запланированных задач нет, файл будет содержать минимум данных, поэтому проверяйте его после того запуска, когда вы точно знаете, что задачи должны были быть выполнены.
В-третьих, выполните сквозную проверку. Запланируйте маркерное событие и проследите за его исчезновением:
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event schedule wp_cli_cron_check now
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event list --fields=hook,next_run_relative --format=csv | grep wp_cli_cron_checkПодождите один интервал, затем снова выполните команду вывода списка. Хук должен исчезнуть, так как однократное событие удаляется из очереди после выполнения. Поскольку ни один плагин не регистрирует обратный вызов для этого имени хука, его выполнение не окажет никакого влияния на сайт. Если хук все еще отображается в списке после двух интервалов, значит, очередь не обрабатывается, а первые две проверки помогут вам понять, в чем проблема: в cron или в WP-CLI.
Не используйте wp cron test для этой цели. Эта команда проверяет, работает ли запуск задач через посещения сайта, и она выдает ошибку, если установлено значение DISABLE_WP_CRON. На правильно настроенном сервере эта ошибка является ожидаемым результатом, а не признаком неисправности.
Альтернатива в виде systemd timer
Если остальные запланированные задачи на сервере уже выполняются через systemd services and timers, перенесите туда и WordPress. Каждый запуск будет отображаться в systemctl list-timers, а вывод будет направляться в журнал, что избавляет от необходимости ротации лог-файлов.
Создайте /etc/systemd/system/wp-cron-example.service:
[Unit]
Description=Run due WordPress cron events for example.com
[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/local/bin/wp --path=/srv/www/example.com cron event run --due-nowЗатем создайте /etc/systemd/system/wp-cron-example.timer:
[Unit]
Description=Run WordPress cron for example.com every 5 minutes
[Timer]
OnCalendar=*:0/5
AccuracySec=30s
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now wp-cron-example.timer
systemctl list-timers wp-cron-example.timer
journalctl -u wp-cron-example.service -n 20Systemd не запускает две копии одного и того же сервиса одновременно, поэтому данная версия не требует flock. Параметр Persistent=true позволяет выполнить пропущенную задачу, если сервер был выключен, чего нельзя добиться с помощью crontab.
Выберите что-то одно: crontab или таймер. Использование обоих методов для одного сайта приведет к тому, что очередь будет обрабатываться дважды, а дублирующиеся запуски задач по отправке email или обработке заказов будут заметны вашим клиентам.
Почему cron-задача не должна выполняться от имени root
Это первый из трех способов, которыми настройка может выйти из строя. Если добавить задачу в crontab пользователя root, WP-CLI остановится, не выполнив никаких действий:
Error: YIKES! It looks like you're running this as root.Очередь никогда не будет обработана, и если вы не перенаправили вывод, вы не увидите сообщение об ошибке. Опасное решение — добавление --allow-root, так как после этого все файлы, созданные плагином во время выполнения, будут принадлежать пользователю root. Следующий веб-запрос выполняется от имени www-data, не может записать данные в эти каталоги, и сайт начинает выдавать ошибки вида:
Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?Исправьте владельца файлов, а затем перенесите задачу:
sudo chown -R www-data:www-data /srv/www/example.com/wp-contentCrontab пользователя root и crontab пользователя www-data — это разные файлы, поэтому удаление строки из одного не затрагивает другой. Проверьте оба:
sudo crontab -u root -l
sudo crontab -u www-data -lПочему cron сообщает об ошибке wp: not found
Это уже второй сбой. Cron предоставляет пользовательским задачам очень короткий PATH, /usr/bin:/bin. WP-CLI устанавливается в /usr/local/bin, которого нет в этом списке. Задача запускается, завершается с ошибкой за долю секунды, и в логе остается одна строка:
/bin/sh: 1: wp: not foundВместо догадок посмотрите на окружение cron самостоятельно. Добавьте временную строку:
*/5 * * * * env > /tmp/cron-env.txt 2>&1Прочитайте /tmp/cron-env.txt после одного интервала выполнения, затем удалите эту строку. Значение PATH= в этом файле — это именно то, что получает ваша задача.
Существует два способа исправления. Используйте абсолютный путь /usr/local/bin/wp, как в шаге 3. Либо задайте PATH один раз в начале crontab, перед всеми строками задач:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binТа же ловушка подстерегает уровнем ниже. Файл wp phar начинается с #!/usr/bin/env php, поэтому оболочка должна иметь возможность найти и php. Если PHP находится вне /usr/bin, что случается при использовании пользовательских сборок или сборок панелей управления, вы получите:
/usr/bin/env: 'php': No such file or directoryВ этом случае вызывайте интерпретатор явно, например, /usr/local/bin/php /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now.
Почему интервал в одну минуту воссоздает исходную проблему
Это уже третий сбой. * * * * * кажется более безопасным вариантом, чем пять минут, но на нагруженном сайте это возвращает вас к исходной точке. Если выполнение задачи занимает больше времени, чем заданный интервал, следующий запуск начнется, пока предыдущий еще не завершился. Через десять минут у вас будет десять процессов PHP, каждый из которых занимает свою память и удерживает собственное соединение с базой данных.
Ищите признаки накопления процессов напрямую:
ps -eo etimes,user,args | grep '[c]ron event run'etimes — это время жизни процесса в секундах. Одна строка означает нормальную работу. Несколько строк с возрастом, значительно превышающим ваш интервал, означают, что запуски накладываются друг на друга. На небольшом VPS это заканчивается ошибкой MySQL Too many connections или тем, что ядро принудительно завершает PHP для освобождения памяти, что можно подтвердить с помощью sudo dmesg -T | grep -i 'killed process'.
WP-CLI выполняет обратные вызовы событий напрямую, а не через запрос wp-cron.php, но в --due-now текущий WP-CLI по-прежнему учитывает транзиент doing_cron — ту же блокировку, которую WordPress использует для защиты от дублирующих запусков. Он устанавливает эту блокировку при старте, и если другой процесс уже удерживает её, новый запуск записывает Warning: A cron event run is already in progress; skipping. в ваш лог и прекращает работу. Эта блокировка длится всего WP_CRON_LOCK_TIMEOUT, по умолчанию 60 секунд, поэтому, если выполнение занимает больше минуты, оно перестает быть защищенным, и следующий запуск может начаться параллельно. flock -n в пункте 3 удерживает свою блокировку всё время жизни процесса, что и предотвращает наложение для медленных задач. Запуск, пропущенный из-за flock, завершается немедленно и без вывода сообщений, так задумано. Наложение — это проблема, которую нужно решать в crontab, а не та, которую ядро хоста решит за вас: даже размещение задач с учетом кэша, добавленное в ядро Linux 7.2 определяет лишь то, на каком ядре окажется процесс, но не то, сколько таких процессов вы запустили.
Выберите интервал, исходя из самого короткого расписания, от которого вы действительно зависите, и сначала замерьте время выполнения:
time sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-nowПять минут — разумное значение по умолчанию: публикация, запланированная на 09:00, обычно происходит к 09:05, если предыдущий запуск завершился. Если в 09:05 предыдущий запуск всё еще удерживает блокировку, этот вызов пропускается, и публикация дожидается следующего запуска, который получит блокировку. Пятнадцать минут — хороший вариант для сайта, где нет критичных по времени задач. Одна минута подходит для магазинов и плагинов, работающих с очередями, которым это действительно необходимо, и только если вы уверены, что выполнение задачи занимает несколько секунд.
Если вы не можете установить WP-CLI
Некоторые хостинг-провайдеры блокируют инструменты командной строки. Обычный HTTP-запрос к wp-cron.php запускает ту же очередь, но через весь стек веб-сервера:
*/5 * * * * /usr/bin/curl -sS --max-time 120 "https://example.com/wp-cron.php?doing_wp_cron" > /dev/nullОт чего вы отказываетесь, прямо:
- Время выполнения ограничено таймаутами веб-сервера и PHP-FPM, поэтому длительная задача может быть прервана на середине.
- Сертификат должен быть действительным, иначе
curlостановится с ошибкойSSL certificate problem, поэтому следите за обновлением сертификатов с помощью Certbot on nginx. - Кэширование страниц не должно затрагивать
wp-cron.php, иначе запросы cron будут получать кэшированный ответ и ничего не выполнится. - Вы не получаете вывод по каждому событию, поэтому единственным доказательством выполнения задачи является её результат.
Флаг -sS позволяет скрыть вывод curl при успешном выполнении, но при этом выводит ошибки, что и требуется для cron-задачи.
Что еще стоит добавить в расписание сервера
Как только системный cron берет на себя управление очередью WordPress, перенесите туда же остальные регулярные задачи сервера, чтобы они были у вас на виду. Установку патчей безопасности операционной системы лучше доверить unattended upgrades, а не прописывать вручную в cron. Обновление плагинов и тем WordPress — это отдельный вопрос: wp plugin update --all в crontab может привести к поломке работающего сайта в 3 часа ночи без присмотра, поэтому выполняйте такие операции осознанно либо используйте промежуточный этап тестирования и резервное копирование.
FAQ
Отключение WP-Cron препятствует публикации запланированных записей?
Нет, если очередь обрабатывается другим способом. DISABLE_WP_CRON лишь отключает запуск очереди при загрузке страниц. События по-прежнему планируются как обычно. Запись, назначенная на 09:00, публикуется при первом запуске cron после 09:00, который успешно получит блокировку, поэтому при пятиминутном интервале публикация обычно происходит к 09:05. Если предыдущий медленный процесс всё ещё удерживает блокировку, этот вызов пропускается, и публикация ожидает следующего запуска. Если вы установили константу, но не добавили запись в cron, запись останется в списке со статусом Missed schedule, пока что-то не обработает очередь.
От имени какого пользователя должен запускаться cron-задача WordPress?
От имени пользователя, которому принадлежат файлы, записываемые PHP; в стандартной установке Ubuntu это www-data. Проверьте это с помощью stat -c '%U %G' /srv/www/example.com/wp-content/uploads и сравните результат со строкой user = в конфигурации пула PHP-FPM. Запуск задачи от имени root приводит к остановке WP-CLI с ошибкой YIKES, а принудительный запуск через --allow-root оставляет в wp-content файлы, принадлежащие root, которые веб-сервер впоследствии не сможет изменить.
Как часто системный cron должен запускать cron WordPress?
Для большинства сайтов подходит интервал в пять минут. Подберите интервал под самое короткое расписание, на которое вы действительно полагаетесь, и держите его с запасом относительно времени выполнения одного цикла. Время выполнения можно измерить, добавив time перед командой WP-CLI. На загруженных сайтах интервалы в одну минуту приводят к наложению процессов друг на друга, если они не защищены flock.
Почему wp cron test выдает ошибку после отключения WP-Cron?
Потому что эта команда проверяет запуск через посетителей, и она сообщает об ошибке, если DISABLE_WP_CRON установлено в true. Это ожидаемый результат для сервера, настроенного таким образом. Вместо этого проверьте путь системного cron: прочитайте /var/log/wp-cron/example.log или запланируйте контрольное событие через wp cron event schedule и убедитесь, что оно исчезло из wp cron event list после следующего запуска.
Нужен ли мне WP-CLI или достаточно curl к wp-cron.php?
Curl работает, и это верное решение, если вы не можете установить WP-CLI. Он медленнее, так как загружает WordPress через веб-сервер и ограничен таймаутом запроса. WP-CLI выполняет события в процессе PHP командной строки без веб-таймаута и выводит по одной строке на каждое событие с указанием длительности, поэтому в логе будет точно видно, что именно выполнилось и сколько времени это заняло.