SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor

Як вимкнути WP-Cron і налаштувати system cron

WP-Cron запускається лише під час відкриття сторінок: на тихих сайтах зависає, на завантажених накопичує чергу. Перенесіть запуск у system cron через WP-CLI та перевірте виконання.

Що таке wp-cron і чому його замінює system cron

WP-Cron — це планувальник завдань, вбудований у WordPress. Він запускається лише тоді, коли хтось відкриває сторінку. Нічого всередині WordPress не запускається самостійно. Під час кожного запиту, який не обслуговується з кешу, WordPress читає список запланованих завдань. Якщо для якогось завдання настав час, WordPress надсилає додатковий HTTP-запит до самого себе на /wp-cron.php, щоб виконати це завдання. Перенесення цього завдання до system cron забезпечує один передбачуваний запуск за фіксованим розкладом — незалежно від того, чи було на сайті тисяча відвідувачів протягом цієї хвилини, чи жодного.

Фактичну роботу виконують два рядки: константа у wp-config.php і запис у crontab. Усе інше в цьому посібнику стосується того, чого не видно з цих двох рядків: від якого користувача має запускатися завдання, як перевірити, що заплановані події справді виконалися, і які є 3 способи непомітної відмови конфігурації без повідомлення на сайті.

У прикладах використано /srv/www/example.com як каталог WordPress і www-data як користувача веб-сервера. Усюди замініть ці значення на власні шляхи та ім’я користувача.

Який відвідувач спричинив витрати cron на завантаженому сайті

Кожен запит без кешу оплачує цю перевірку ресурсами. WordPress завантажує опцію cron, порівнює часові мітки, а коли настає час виконання завдання, викликає spawn_cron(). Ця функція надсилає неблокувальний loopback-запит до /wp-cron.php. Відвідувач не чекає на результат. Але PHP worker очікує. На невеликому VPS із PHP-FPM і pm.max_children = 5 одне повільне заплановане завдання займає п’яту частину доступної потужності PHP на весь час виконання. Найімовірніше, воно запуститься саме протягом найзавантаженішої хвилини, оскільки тоді надходить найбільше запитів на сторінки.

WordPress обмежує дублікати. Він встановлює блокування з часом життя WP_CRON_LOCK_TIMEOUT — за замовчуванням 60 seconds. Тому одночасні відвідувачі не запускають окремий процес кожен. Блокування обмежує дублювання. Але воно не прибирає виконання завдання зі шляху обробки запиту.

Перш ніж вирішувати, чи має це значення, перевірте частоту запусків на власному сервері. Кожен loopback-запит з’являється в access log веб-сервера:

sudo grep -c 'wp-cron.php' /var/log/nginx/access.log

Apache натомість записує їх до /var/log/apache2/access.log. Кілька тисяч таких запитів на день — це реальні витрати. Такі показники потрібно вимірювати на власному сервері, а не брати зі статті. Так само слід порівняти продуктивність VPS до та після будь-якої іншої зміни.

Кешування змінює ситуацію. Якщо page cache обслуговує більшість запитів як статичний 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, і зазвичай це безпечно, оскільки файл запускає лише завдання, для яких настав час. Блокування цього URL у конфігурації веб-сервера є необов’язковим. Якщо ви його заблокуєте, резервний механізм curl наприкінці цього посібника також перестане працювати.

Крок 2: інсталяція WP-CLI

WP-CLI — офіційний інструмент командного рядка для WordPress. Для його роботи потрібен PHP command line binary. Це окремий пакет, не модуль 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 --info

php wp-cli.phar --info виводить шлях до PHP binary, версію PHP і версію WP-CLI. Якщо виведено всі три значення, phar працює. Станом на October 2026 посібник з інсталяції вказує PHP 7.2.24 як мінімальну версію, а Ubuntu 24.04 постачається з PHP 8.3. Тому на актуальному сервері цей поріг перевищено. Пізніше оновлюйте WP-CLI за допомогою 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 не працює, оскільки login shell цього облікового запису — /usr/sbin/nologin, і ви отримуєте This account is currently not available.. Передавання команди безпосередньо до sudo -u минає login shell, тому команда виконується успішно.

Тепер перевірте, чи сам 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 pool із власним користувачем, що зазвичай є результатом окремого налаштування сайту в LAMP stack на 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 захоплює lock file і негайно завершує роботу, якщо попередній запуск досі утримує блокування. /usr/local/bin/wp — це абсолютний шлях, який потрібен cron. --path дає змогу виконувати команду з будь-якого робочого каталогу. --due-now обробляє лише події, час яких настав, а не кожну подію в черзі. Перенаправлення записує стандартний вивід і помилки в один файл, який можна прочитати.

На практиці це перенаправлення обов’язкове. Cron надсилає вивід завдання його користувачу електронною поштою, але в більшості VPS-образів не встановлено mail transfer agent. Тоді 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 записує повідомлення до журналу через власний unit:

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.log

WP-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

Зачекайте один інтервал, а потім знову виконайте команду для перегляду списку. Hook зникне, оскільки одноразова подія видаляється з черги після виконання. Жоден плагін не реєструє callback для цього імені hook, тому його запуск більше нічого не змінює на сайті. Якщо після двох інтервалів hook усе ще відображається у списку, черга не обробляється. Перші дві перевірки покажуть, проблема в cron чи в WP-CLI.

Не використовуйте для цього wp cron test. Ця команда перевіряє, чи працює запуск процесу за запитом відвідувача, і повертає помилку, коли виконується умова DISABLE_WP_CRON. На правильно налаштованому сервері ця помилка є очікуваним результатом, а не несправністю.

Альтернатива з timer systemd

Якщо інші заплановані завдання на сервері вже виконуються через сервіси й timer systemd, додайте туди й 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.target
sudo 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 20

systemd не запускатиме одночасно дві копії одного сервісу, тому цій версії не потрібен flock. Persistent=true дає змогу виконати пропущений запуск після вимкнення машини. Запис у crontab цього не робить.

Виберіть crontab або timer. Якщо запускати обидва варіанти для одного сайту, черга оброблятиметься двічі. Повторні запуски завдань електронної пошти або замовлень будуть помітні вашим клієнтам.

Чому cron job не повинен запускатися від імені root

Це перший із трьох способів, через які налаштування не працює. Додайте job до crontab root, і WP-CLI завершить роботу ще до виконання будь-яких дій:

Error: YIKES! It looks like you're running this as root.

Черга не запускається. Якщо ви не перенаправили вивід, повідомлення не буде видно. Небезпечне виправлення — додати --allow-root, оскільки тоді всі файли, які plugin створює під час цього запуску, належатимуть root. Наступний вебзапит виконується від імені www-data і не може записувати до цих каталогів. Сайт починає повідомляти, наприклад:

Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?

Виправте власника, а потім перенесіть job:

sudo chown -R www-data:www-data /srv/www/example.com/wp-content

crontab 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

Ця сама проблема виникає на наступному рівні. Файл phar wp починається з #!/usr/bin/env php, тому shell також має знаходити 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 запускає callback-и подій безпосередньо, а не надсилає запит до wp-cron.php, але в поточній версії --due-now WP-CLI усе ще враховує transient doing_cron — той самий блокувальник, який WordPress використовує для запобігання дубльованим запускам. Коли запуск починається, WP-CLI встановлює цей блокувальник. Якщо інший запуск уже утримує його, новий запуск записує Warning: A cron event run is already in progress; skipping. у журнал і завершується. Цей блокувальник діє лише WP_CRON_LOCK_TIMEOUT, а типовим значенням є 60 секунд. Тому запуск, який триває довше за хвилину, перестає бути захищеним ним, і наступний запуск може початися паралельно. flock -n у записі кроку 3 утримує блокувальник, доки працює процес, що запобігає накладанню повільних запусків. Запуск, пропущений через flock, за задумом одразу й без повідомлення завершується. Накладання запусків потрібно усувати в crontab, а не покладатися на ядро хоста: навіть розміщення завдань із урахуванням кешу, додане в Linux kernel 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 на nginx.
  • Кешування сторінок не має кешувати wp-cron.php, інакше cron-запити отримуватимуть кешовану відповідь, а нічого не виконуватиметься.
  • Ви не отримуєте окремих результатів для кожної події, тому єдиною ознакою виконання завдання буде його результат.

-sS не виводить повідомлень під час успішного виконання, але продовжує виводити помилки. Саме це потрібно для завдання cron.

Що ще варто запланувати на сервері

Після того як системний cron обробляє чергу WordPress, зберігайте інші регулярні завдання сервера в тому самому місці, де їх можна переглядати. Оновлення безпеки операційної системи слід доручити автоматичним оновленням, а не рядку cron, який ви підтримуєте вручну. Оновлення плагінів і тем WordPress потребують окремого рішення: wp plugin update --all у crontab може непомітно зламати робочий сайт о 3:00, тому виконуйте їх свідомо або після етапу перевірки в staging-середовищі та створення резервної копії.

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 залишає файли, власником яких є root, у каталозі wp-content. Після цього вебсервер не зможе їх записувати.

Як часто system cron має запускати WordPress cron?

Кожні п’ять хвилин підходить для більшості сайтів. Виберіть інтервал відповідно до найкоротшого розкладу, на який ви справді покладаєтеся. Інтервал також має бути помітно довшим за час одного запуску. Виміряти цей час можна, додавши time перед командою WP-CLI. На завантаженому сайті інтервали в одну хвилину можуть накладати запуски один на одного, якщо їх не захищає flock.

Чому wp cron test завершується помилкою після вимкнення WP-Cron?

Тому що ця команда перевіряє запуск WP-Cron через відвідувачів і повідомляє про помилку, коли DISABLE_WP_CRON має значення true. Це правильний результат для сервера, налаштованого таким чином. Перевірте шлях system 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-процесі командного рядка без вебтайм-ауту. Він виводить по одному рядку для кожної події із зазначенням її тривалості. Тому журнал точно показує, що саме виконалося і скільки часу це тривало.