Якщо ви коли-небудь бачили, як “запланований” пост WordPress пропускає час публікації, ніби не отримав запрошення на зустріч, — ви познайомились із WP-Cron у його природному середовищі: “робить усе, що може”. Іноді він працює місяцями. Потім трафік падає, кеш змінює поведінку або PHP-FPM починає капризувати — і раптом ваш контент-календар виявляється неправдивим.
Це не якась містична проблема WordPress. Це система планування, побудована на веб-запитах, яка працює всередині стека, що любить оптимізувати те, що їй потрібно: реальний, надійний тригер. Повернемо це до нудної, стабільної інженерії.
WP-Cron — це не cron (і в цьому вся проблема)
Linux cron — це планувальник. Він виконується незалежно від того, чи є на сайті трафік. WP-Cron — це планувальник на рівні застосунка, який запускається тому що на сайт надходить запит. Ота єдина відмінність пояснює більшість інцидентів із “пропущеними розкладом”.
WP-Cron запускається, коли WordPress отримує запит і помічає, що заплановані завдання настав час виконувати. Потім він робить “loopback” HTTP-запит до wp-cron.php (або виконує його інлайн, залежно від конфігурації та середовища). Іншими словами: WordPress планує роботу, але не контролює годинник. Годинник — це ваші відвідувачі.
Коли трафік стабільний і стек дозволяє, WP-Cron “працює”. Коли трафік низький, або запити кешуються, або loopback-блоки стоять на шляху — WP-Cron не спрацьовує. Заплановані пости не публікуються. Підписки WooCommerce не продовжуються. Сканування безпеки не виконується. І ви дізнаєтесь, від чого насправді залежить ваш бізнес.
Є ще запах ненадійності: WP-Cron живе в PHP, за веб-серверами, кешами, WAF і тайм-аутами. Якщо ваш механізм запуску cron пов’язано з тим самим шляхом, який обслуговує кінцевих користувачів, ви побудували планувальник, який може бути порушений через… обслуговування кінцевих користувачів.
Суха істина: якщо вчасна публікація важлива, потрібен реальний планувальник (system cron або job runner), що викликає WordPress у відомому інтервалі. WP-Cron — запасний варіант. Ставтесь до нього як до такого.
Жарт #1: WP-Cron — як кухонний таймер, який дзвонить лише якщо хтось відкрив холодильник.
Як це ламається в продакшні: симптоми, які ви побачите
Збої рідко проявляються як чистий сигнал “cron зламано”. Вони проявляються як дивні бізнесові симптоми. Трюк — швидко зіставити симптоми з режимами відмови.
Класичні симптоми
- Заплановані пости зависли в статусі “Scheduled” або “Missed schedule”. Час публікації пройшов; нічого не сталося.
- Черга “Scheduled Actions” WooCommerce росте. Підписки, листи про покинутий кошик, синхронізація запасів, вебхуки — тихо затримуються.
- Резервні копії не виконуються. Плагіни, що покладаються на WP-Cron, не отримують CPU.
- Оновлення індексу пошуку / sitemap відстають. SEO-інструменти покладаються на заплановані завдання; вихід застаріває.
- Випадкові сплески фону. Раптовий стрибок трафіку запускає накопичення завдань, спричиняючи повільні запити та тайм-аути.
Що зазвичай відбувається під капотом
- Loopback-запит заблокований або зламаний. WordPress не може викликати сам себе через DNS, брандмауер, WAF, авторизацію, TLS або редиректи.
- Запити взагалі не доходять до WordPress. Повноцінний кеш сторінки повертає відповіді, не зачіпаючи PHP, тому WP-Cron ніколи не отримує “тику”.
- Головні PHP-процеси зайняті. Навіть якщо cron спрацьовує, він не виконується через брак FPM-воркерів.
- Тайм-аути. Cron стартує, його вбивають, залишається блокування/транзієнт, і нічого не виконується, поки блокування не закінчиться.
- Проблеми з годинником. Зсув часу або невідповідні налаштування часового поясу плутають планування.
Швидкий план діагностики
Ось у якому порядку я перевіряю речі, коли бізнес каже “заплановані пости не публікувались” і я хочу відповідь до наступного стендапу.
Перше: чи WordPress намагається планувати події?
- Виведіть список очікуваних cron-подій через WP-CLI.
- Якщо черга порожня, проблема може бути в логіці плагіна/теми або в конфузі з часовим поясом.
- Якщо черга наповнена простроченими подіями, виконавець не запускається.
Друге: чи може сайт викликати сам себе?
- З сервера зробіть curl до
wp-cron.phpі перевірте HTTP-код та затримку. - Тести loopback з самого WordPress можуть падати через DNS або заблокований вихідний трафік.
- Перевірте редиректи на логін, проблеми з примусовим HTTPS або виклики WAF.
Третє: чи взагалі доступний PHP для виконання?
- Перевірте стан PHP-FPM: досягнуто max_children, повільні логи, backlog.
- Перевірте журнали веб-сервера на предмет upstream тайм-аутів і сплесків 502/504.
- Підтвердіть, що OPcache та автозавантаження в порядку (cron часто виконує рідко зачіпані шляхи коду).
Потім: приберіть “трафік” з рівняння
- Вимкніть тригери WP-Cron і запустіть WordPress cron через system cron кожну хвилину або кожні п’ять хвилин.
- Перевірте прострочені події; вони повинні predictable — очиститися.
Цей план упереджений у бік швидкого знаходження вузького місця. Він не чемний. Він працює.
Практичні завдання: команди, виводи та рішення (12+)
Ось виконувані команди для типової Linux-машини з Nginx/Apache + PHP-FPM і WordPress. Налаштуйте шляхи та користувачів, але зберігайте суть. Кожне завдання включає: команду, приклад виводу, що це означає і яке рішення приймати.
Завдання 1: Підтвердити час системи та часовий пояс на рівні ОС
cr0x@server:~$ timedatectl
Local time: Sat 2025-12-27 14:31:19 UTC
Universal time: Sat 2025-12-27 14:31:19 UTC
RTC time: Sat 2025-12-27 14:31:18
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Що це означає: Годинник ОС — UTC і синхронізований. Добре. Якщо тут буде System clock synchronized: no або дивний часовий пояс, заплановані пости можуть зсуватися або виглядати як “пропущені”.
Рішення: Якщо NTP не активний/не синхронізований, виправте час спочатку. Діагностика cron на рухомому годиннику — мов полювання за рухомою ціллю.
Завдання 2: Перевірити часовий пояс WordPress і поточний час через WP-CLI
cr0x@server:~$ cd /var/www/html
cr0x@server:/var/www/html$ wp option get timezone_string
America/New_York
cr0x@server:/var/www/html$ wp eval 'echo "wp_time: ".wp_date("c").PHP_EOL;'
wp_time: 2025-12-27T09:31:33-05:00
Що це означає: WordPress використовує налаштування часового поясу сайту. Якщо воно порожнє, WordPress може покладатися на офсет UTC, і поведінка при переході на DST може бути дивною.
Рішення: Віддавайте перевагу іменованому часовому поясу (наприклад, America/New_York) замість сирого офсету. Так менше сюрпризів під час літнього/зимового часу.
Завдання 3: Чи WP-CLI говорить із потрібним WordPress?
cr0x@server:/var/www/html$ wp core version
6.7.1
cr0x@server:/var/www/html$ wp site list
+----+---------------------+----------------------------+
| id | url | last_updated |
+----+---------------------+----------------------------+
| 1 | https://example.com | 2025-12-27 14:29:05 +0000 |
+----+---------------------+----------------------------+
Що це означає: Ви працюєте з очікуваною інсталяцією і (якщо multisite) з правильною мережею.
Рішення: Якщо це помилкує, виправте права або параметр --path перед продовженням. Діагностика “в сліпу” — як лагодити продакшн на підозрах.
Завдання 4: Перелічити cron-події та знайти прострочені
cr0x@server:/var/www/html$ wp cron event list --fields=hook,next_run,recurrence --format=table | head
+-------------------------------+---------------------+------------+
| hook | next_run | recurrence |
+-------------------------------+---------------------+------------+
| wp_version_check | 2025-12-27 13:55:00 | twice_daily|
| wp_scheduled_delete | 2025-12-27 14:00:00 | daily |
| action_scheduler_run_queue | 2025-12-27 14:01:00 | every_minute|
| wp_update_plugins | 2025-12-27 12:10:00 | twice_daily|
+-------------------------------+---------------------+------------+
Що це означає: Якщо next_run в минулому на хвилини/години і не виконується, виконавець cron зламаний або заблокований.
Рішення: Якщо події прострочені, переходьте до перевірок loopback і виконуючого процесу. Якщо подій зовсім немає, розслідуйте плагіни/тему або логіку планування.
Завдання 5: Запустити cron вручну зараз і подивитися, що станеться
cr0x@server:/var/www/html$ wp cron event run --due-now
Success: Executed a total of 12 cron events.
Що це означає: WordPress може виконувати події за запитом. Це підказує, що проблема в тригері/виконавці, а не у самих обробниках подій.
Рішення: Якщо ручне виконання працює, впровадьте system cron і вимкніть тригери WP-Cron. Якщо ручний запуск падає з fatal-ами, йдіть у PHP-журнали і ізоляцію плагінів.
Завдання 6: Перевірити, чи встановлено DISABLE_WP_CRON
cr0x@server:/var/www/html$ grep -n "DISABLE_WP_CRON" wp-config.php
91:define('DISABLE_WP_CRON', true);
Що це означає: WP-Cron вимкнено. Це нормально тільки якщо у вас є реальний системний cron, що його замінює.
Рішення: Якщо це true і системного cron немає — ви знайшли баг. Додайте системний cron job (див. розділ виправлень).
Завдання 7: Перевірити системний cron на наявність раннера для WordPress
cr0x@server:~$ sudo crontab -l
no crontab for root
Що це означає: Для root немає crontab. Ранер може існувати під веб-користувачем або в /etc/cron.d.
Рішення: Перевірте crontab відповідного користувача та системні директорії cron.
cr0x@server:~$ crontab -l
# m h dom mon dow command
*/5 * * * * cd /var/www/html && wp cron event run --due-now --quiet
Що це означає: Є раннер кожні 5 хвилин, що використовує WP-CLI. Це адекватна база.
Рішення: Якщо заплановані пости досі пропадають, раннер може не виконуватися (права, PATH, PHP) або завдання фейляться.
Завдання 8: Підтвердити, що cron дійсно запускається (перевірити syslog/journal)
cr0x@server:~$ sudo journalctl -u cron --since "1 hour ago" | tail -n 10
Dec 27 14:25:01 server CRON[21901]: (cr0x) CMD (cd /var/www/html && wp cron event run --due-now --quiet)
Dec 27 14:30:01 server CRON[22188]: (cr0x) CMD (cd /var/www/html && wp cron event run --due-now --quiet)
Що це означає: Демон cron виконує завдання за розкладом.
Рішення: Якщо записів немає, cron може бути зупинений, замаскований або журнали пишуться в інше місце. Виправте платформу перш за все.
Завдання 9: Підтвердити, що команда wp-cli працює в середовищі cron
cr0x@server:~$ env -i HOME=/tmp PATH=/usr/bin:/bin bash -lc 'cd /var/www/html && wp cron event run --due-now'
Success: Executed a total of 0 cron events.
Що це означає: Навіть із мінімальним середовищем (схожим на cron), WP-CLI запускається. “0 events” може бути припустимим, якщо нічого не має бути виконано.
Рішення: Якщо це падає, вкажіть повні шляхи в crontab і переконайтесь, що WP-CLI встановлено там, де cron його знайде.
Завдання 10: Протестувати loopback-доступ до wp-cron.php з сервера
cr0x@server:~$ curl -I -sS https://example.com/wp-cron.php?doing_wp_cron=1 | head -n 5
HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0
date: Sat, 27 Dec 2025 14:32:10 GMT
Що це означає: Ендпоінт доступний і повертає 200. Добре. Якщо ви бачите 301/302 петлі, 403, 503 або сторінку із викликом WAF, тригери WP-Cron можуть не спрацьовувати.
Рішення: Якщо loopback не працює, виправте DNS/TLS/редиректи/WAF правила або обходьте loopback, використовуючи system cron з WP-CLI.
Завдання 11: Перевірити, чи повноцінний кеш заважає зверненням до PHP
cr0x@server:~$ curl -sS -I https://example.com/ | egrep -i 'x-cache|cf-cache-status|age|server|via'
server: nginx
x-cache: HIT
age: 1842
Що це означає: Запити можуть обслуговуватися без звернення до PHP. Якщо сайт має низький трафік і в більшості кешується, WP-Cron майже не отримає тригерів.
Рішення: Не покладайтесь на відвідувачів для планування. Використайте system cron. Опційно виключіть /wp-cron.php з кешування та обмеження запитів.
Завдання 12: Перевірити навантаження PHP-FPM (голод по воркерам)
cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm.log
[27-Dec-2025 14:20:55] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
[27-Dec-2025 14:20:56] WARNING: [pool www] child 18734, script '/var/www/html/wp-cron.php' (request: "GET /wp-cron.php?doing_wp_cron=...") executing too slow (12.345 sec), logging
Що це означає: Cron намагається виконуватись, але PHP насичений або повільний. WP-Cron запити можуть тайм-аутитись або ніколи не стартувати.
Рішення: Збільшіть capacity (pm.max_children), зменшіть навантаження запитів, перемістіть cron у окремий пул або відвантажте важкі задачі.
Завдання 13: Шукати фатальні помилки WordPress під час cron
cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/27 14:21:03 [error] 1229#1229: *9981 FastCGI sent in stderr: "PHP Fatal error: Uncaught Error: Call to undefined function mb_strlen() in /var/www/html/wp-includes/formatting.php:..."
2025/12/27 14:21:03 [error] 1229#1229: *9981 upstream prematurely closed FastCGI stdout while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /wp-cron.php?doing_wp_cron=..."
Що це означає: Cron впав через PHP fatal (в цьому прикладі — відсутнє розширення). Це може залишити події простроченими назавжди.
Рішення: Виправте базове середовище PHP. Cron не особливий; він зачіпає шляхи коду, які звичайні перегляди сторінок можуть не зачіпати.
Завдання 14: Ідентифікувати конкретний hook, що накопичується (Action Scheduler)
cr0x@server:/var/www/html$ wp action-scheduler list --status=pending --per-page=5
+----------+------------------------------+---------------------+----------+
| hook | args | scheduled_date_gmt | status |
+----------+------------------------------+---------------------+----------+
| wc_email | {"id":12345,"type":"follow"} | 2025-12-27 13:58:00 | pending |
| wc_email | {"id":12346,"type":"follow"} | 2025-12-27 13:59:00 | pending |
| wc_email | {"id":12347,"type":"follow"} | 2025-12-27 14:00:00 | pending |
+----------+------------------------------+---------------------+----------+
Що це означає: Action Scheduler від WooCommerce має беклог. Це може бути “cron не працює” або “cron працює, але не встигає”.
Рішення: Запустіть чергу вручну для тесту, потім масштабуйте раннер або виправляйте продуктивність.
Завдання 15: Запустити Action Scheduler чергу вручну (контрольований тест)
cr0x@server:/var/www/html$ wp action-scheduler run --batch-size=25
Processed 25 actions.
Що це означає: Завдання можуть виконуватись. Якщо це повільно або з помилками — у вас навантаження/продуктивність або баг плагіна, а не лише тригер.
Рішення: Якщо ручні запуски успішні, налаштуйте належний раннер. Якщо він падає — ізолюйте плагін/hook і вирішуйте корінну причину.
Завдання 16: Перевірити, чи база даних не є прихованим вузьким місцем (блокування, повільні запити)
cr0x@server:~$ sudo mysql -e "SHOW PROCESSLIST;" | head
Id User Host db Command Time State Info
42 wpuser localhost wordpress Query 12 Waiting for table metadata lock UPDATE wp_options SET option_value='...' WHERE option_name='cron'
77 wpuser localhost wordpress Query 2 Sending data SELECT * FROM wp_actionscheduler_actions WHERE status='pending' LIMIT 0, 25
Що це означає: Оновлення cron (збережені в wp_options) можуть блокуватись metadata locks. Важкі зміни схеми, довгі транзакції або погані запити можуть гальмувати планування.
Рішення: Якщо бачите очікування блокувань, знайдіть процес-блокер, скоротіть довгі транзакції і уникайте виконання DDL під навантаженням. Cron покладається на швидкі записи опцій.
Виправлення, що працюють: від тимчасових латок до реального планувальника
Виправлення 1: Перестаньте покладатись на трафік — використайте system cron + WP-CLI
Це доросле виправлення. Якщо ви на VPS, bare metal або будь-якому хості, де контролюєте cron — зробіть це.
- Вимкніть тригер WP-Cron, додавши в
wp-config.php:cr0x@server:~$ sed -n '1,120p' /var/www/html/wp-config.php | tail -n 5 define('DB_COLLATE', ''); define('DISABLE_WP_CRON', true); /* That's all, stop editing! Happy publishing. */ - Додайте crontab-запис (кожну 1–5 хвилин залежно від навантаження):
cr0x@server:~$ crontab -e */1 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now --quiet
Чому WP-CLI краще за виклик wp-cron.php? Менше рухомих частин. Немає WAF. Немає TLS. Немає редиректів. Немає кешу. Просто PHP виконує WordPress у контрольованому контексті.
Компроміс: Потрібно тримати WP-CLI встановленим і доступним. Це не складно. Це файл.
Виправлення 2: Якщо WP-CLI не можна запустити, виконайте wp-cron.php з cron
Іноді ви на хостингу з обмеженнями або платформа не має WP-CLI. Ви все ще можете використати system cron для виклику ендпоінта, але ви повертаєтесь до HTTP-фрагильності.
cr0x@server:~$ crontab -e
*/2 * * * * curl -sS -o /dev/null https://example.com/wp-cron.php?doing_wp_cron=1
Рішення: Використовуйте це лише якщо іншого варіанту немає. Якщо він іноді падає через WAF/ліміти, проблема поступово повернеться.
Виправлення 3: Виправте loopback-збої (403/401/редиректи/WAF)
Loopback-збої найдратливіші, бо сайт працює для людей, а виклик самого себе — ні.
- Basic auth на стейджингу або продакшні: Loopback отримує 401. Вирішіть, дозволивши loopback IP або налаштуйте cron використовувати WP-CLI.
- Примусове HTTPS / петлі редиректів: Неправильні
siteurl/homeабо заголовки проксі. Виправте конфігурацію зворотного проксі та параметри URL у WordPress. - WAF/захист від ботів: Виклики блокуються. Виключіть
/wp-cron.phpабо припиніть використовувати HTTP loopback зовсім.
Виправлення 4: Виключіть wp-cron.php з кешування та агресивного rate limiting
Навіть якщо ви запускаєте cron через system cron, можуть бути плагіни, що все одно роблять loopback. Зробіть ендпоінти cron “нудними”.
Принаймні, не кешуйте /wp-cron.php і не застосовуйте до нього bot challenges. Якщо ваш CDN наполягає на “допомозі”, зрештою він допоможе вам пропустити розклад.
Виправлення 5: Виділити PHP-ресурси для cron (серйозні сайти так роблять)
Якщо фронтенд WordPress навантажений, cron конкурує за ті самі воркери. Під навантаженням cron голодує. Або навпаки: cron запускається і уповільнює фронтенд. Оберіть, що прийнятніше.
Опції:
- Виділений пул PHP-FPM для адмінки та cron-шляхів із власними лімітами.
- Окремий хост/контейнер для раннера, що викликає WP-CLI проти того самого коду та БД.
- Виносити важкі задачі з WordPress зовсім (відправка листів, експорт, формування фідів) і дозволити WP лише ставити їх у чергу.
Виправлення 6: Справитись із “cron lock” та завислими транзієнтами
WordPress використовує механізм блокування (збережений в опціях/транзієнтах), щоб уникнути одночасних запусків cron. Якщо процес помирає під час виконання, може з’явитись шаблон “cron заблоковано”, де нічого не виконується, поки не вийде час блокування.
Краще діагностувати, чому cron падає (фатали, пам’ять, тайм-аути), але коли потрібно розблокувати продакшн:
cr0x@server:/var/www/html$ wp option list --search=cron --fields=option_name,autoload --format=table | head
+----------------+----------+
| option_name | autoload |
+----------------+----------+
| cron | yes |
+----------------+----------+
Рішення: Не видаляйте опцію cron; це сховище розкладу. Якщо підозрюєте застаріле блокування, працюйте з транзієнтом doing_cron, але тільки після підтвердження, що активний запуск справді відсутній.
Виправлення 7: Зробіть публікацію стійкою (не ставте бізнес на один механізм)
Якщо вчасна публікація критична для доходу (реклама, кампанії, юридичні оголошення), розгляньте підхід “ремінь і підтяжки”:
- System cron працює кожну хвилину.
- Зовнішній монітор перевіряє прострочені пости і сповіщає SRE/ops у разі повтору.
- Редактори отримують повідомлення в UI про пропущені розклади (це не виправлення, але швидший сигнал).
Цитата (парафраз): Вернер Фогельс неодноразово підкреслював ідею, що “усе ламається постійно”, тому системи мають бути спроектовані з очікуванням і толерантністю до відмов.
Жарт #2: Якщо ваш план реагування на інциденти — “оновлювати головну сторінку, поки не опублікується”, вітаю — ви винайшли людський cron.
Типові помилки: симптом → корінна причина → виправлення
Тут ми припиняємо бути ввічливими до наших минулих рішень.
1) Заплановані пости ніколи не публікуються на сайтах з низьким трафіком
Симптом: Пости, заплановані вночі, не публікуються, доки хтось не відвідає сайт.
Корінна причина: WP-Cron залежить від завантажень сторінок для тригера.
Виправлення: Вимкніть тригери WP-Cron і виконуйте system cron з WP-CLI кожну 1–5 хвилин.
2) “Missed schedule” після ввімкнення нового кеша/CDN
Симптом: Все було добре; потім ввімкнули кеш — і заплановані події зупинились.
Корінна причина: Кеш повертає сторінки без PHP, видаляючи “тику” WP-Cron. Іноді сам wp-cron.php потрапляє в кеш або піддається rate-limiting.
Виправлення: Використайте system cron. Виключіть /wp-cron.php з кешування і WAF-викликів.
3) wp-cron.php повертає 403 тільки зі сервера
Симптом: Браузери отримують 200; loopback зі сервера повертає 403 або сторінку з challenge.
Корінна причина: Правила WAF трактують запити з сервера як боти; або вихідна IP відрізняється; або заголовки не відповідають очікуваним.
Виправлення: Додайте у whitelist egress IP сервера для /wp-cron.php, або припиніть використовувати loopback і запустіть WP-CLI з cron.
4) Cron запускається, але беклог ніколи не зникає
Симптом: Події cron прострочені, ручний запуск виконує кілька, але купа лишається.
Корінна причина: Навантаження занадто важке для частоти/пропускної здатності; Action Scheduler потребує більшого throughput; тайм-аути PHP вбивають виконання в середині батчу.
Виправлення: Збільшіть частоту раннера, обережно підвищуйте batch size, додайте PHP-ресурсів і дослідіть повільні hooks. Розгляньте окремі воркери.
5) DISABLE_WP_CRON встановлено без реального раннера
Симптом: Після “performance tuning” усе заплановане перестало виконуватися.
Корінна причина: Хтось вимкнув WP-Cron, щоб “зменшити запити”, і забув додати заміну у вигляді system cron.
Виправлення: Додайте system cron з WP-CLI; перевірте журнали, що він виконується.
6) Loopback ламається через неправильні URL після міграції
Симптом: Ендпоінт cron редиректить по колу або потрапляє на неправильний домен.
Корінна причина: home/siteurl не співпадають або заголовки проксі не налаштовані, через що WordPress генерує неправильні внутрішні URL.
Виправлення: Виправте опції URL, налаштуйте заголовки проксі та повторно протестуйте curl до wp-cron.php.
7) Cron падає тільки під час пікового навантаження
Симптом: Вночі все ок; під час запусків/розпродажів — пропущені розклади.
Корінна причина: Насичення PHP-FPM; cron-запити ставляться в чергу або вбиваються.
Виправлення: Відокремте cron в окремий пул/хост, збільшіть FPM-місткість, зменшіть важкі плагіни і обмежте навантаження cron.
8) “Випадкові” збої виявились через DNS
Симптом: loopback до wp-cron.php час від часу тайм-аутиться.
Корінна причина: Сервер резолвить публічний домен у зовнішню IP, робиться hairpin через фаєрвол/CDN, або виникають проблеми з IPv6; інколи внутрішній DNS відрізняється від публічного.
Виправлення: Використайте WP-CLI раннер. Якщо loopback потрібен — виправте DNS/розв’язування і віддавайте перевагу викликам через localhost, коли можливо.
Три корпоративні історії з країн “має ж було працювати”
Міні-історія №1: Неправильне припущення (“Cron працює, бо є трафік”)
Маркетингова команда середньої SaaS-компанії винесла блог WordPress за блискучий новий CDN. Швидкість сторінки покращилась. Бали Lighthouse стали предметом гордості. Усі були задоволені.
Дві тижні потому запланований пост із анонсом вебінару не опублікувався. Хтось помітив це за годину після початку. Початкова теорія — помилка редактора: “Вони забули натиснути Publish.” Друга теорія — часові пояси: “Може він у UTC?” Класична гра в звинувачення.
На сервері черга cron показувала прострочені події. Ручний запуск wp cron event run --due-now миттєво опублікував пост. Це було підказкою: система могла виконувати — просто її не тригерували.
Неправильне припущення було тонким: “У нас є трафік, отже WP-Cron працюватиме.” Але CDN повертав більшість сторінок із кешу. PHP не викликався. Відвідувачі були реальні, але їхні запити не доходили до WordPress. WP-Cron фактично був відключений.
Виправлення було простим і трохи прикрим: вимкнули тригери WP-Cron, встановили WP-CLI і додали system cron щохвилини. Після цього публікація залежала від часу, а не від переглядів сторінки.
Міні-історія №2: Оптимізація, що дала протилежний ефект (“Вимкнути wp-cron.php заради продуктивності”)
Команда ops успадкувала кластер WordPress з періодичними сплесками латентності. Хтось прочитав статті з порадами, що wp-cron.php “повільний” і вирішив оптимізувати. Вони заблокували доступ до /wp-cron.php на краю і встановили DISABLE_WP_CRON в wp-config.php.
У відриві це не був абсурд. Але заміни системним cron не додали. Сайт здавався в порядку, бо фронтенд все ще обслуговував контент. Перший видимий збій стався через кілька днів: підписки WooCommerce не поновились вчасно, і накопичилася черга “pending”.
Коли вони розслідували, backlog був настільки великий, що повторне вмикання cron викликало майже DDoS власними силами. Перший відновлювальний запуск намагався виконати години прострочених завдань під піковим навантаженням. PHP-FPM здалися. Клієнти отримали 502. Виправлення спричинило другий інцидент.
Рішення полягало не лише в “увімкнути cron назад”. Вони ввели контрольований раннер: WP-CLI cron щохвилини, налаштували cadence Action Scheduler і обмежили розміри батчів. Також навчилися трактувати зміни “disable” як production-зміну з планом відкату.
Оптимізація — чудо. Оптимізація без плану заміни — просто повільніший інцидент.
Міні-історія №3: Нудна практика, що врятувала день (окремий раннер + моніторинг)
Видавець з кількома сайтами WordPress вже отримував досвід на гіркому: вони не довіряли WP-Cron, тож побудували нецікаву, але ефективну систему: кожен сайт мав DISABLE_WP_CRON і системний cron job, що запускав WP-CLI. Вони логували час виконань і рахували прострочені події як метрику.
Одного ранку проблема зі збереженням на VM спричинила I/O латентність. Фронтенд був здебільшого доступний завдяки кешуванню, але PHP-процеси сповільнилися. Cron job все ще запускався, але почав виконуватись довше, і метрика “прострочені події” почала зростати.
Ніхто не чекав на скаргу редакторів. Алерт спрацював, коли прострочені події перевищили поріг кілька хвилин підряд. Інженер на чергуванні подивився логи, побачив попередження про повільний PHP-FPM і зв’язав це з дисковою латентністю. Вони тимчасово переключили виконання cron на менш уражений хост і обмежили фонові завдання, поки сховище не стабілізувалось.
Чи публікувались заплановані пости вчасно? В основному так. Деякі затримались на кілька хвилин, але нічого не пропустилося на годину. Бізнес майже не відчув змін. Ось мета: відмови, які відбуваються тихо, бо ваші нудні контролі їх виявили раніше.
Вони перемогли не тому, що були кмітливими. Вони перемогли тому, що були готові.
Цікаві факти та контекст (чому така архітектура існує)
WP-Cron здається дивним, якщо ви прийшли зі світу традиційних систем. Це стає менш дивним, якщо згадати середовища, для яких спочатку створювався WordPress.
- WP-Cron існує здебільшого тому, що на shared-hosting не завжди був надійний доступ до cron. WordPress мусив планувати завдання, не припускаючи привілеїв на рівні ОС.
- Це “псевдо-cron” за задумом. Він підсаджується на запити сторінок, щоб апроксимувати планування по часу.
- WordPress зберігає розклади cron у базі даних (в
wp_options). Це робить їх переносимими, але також робить їх чутливими до блокувань БД і повільних записів. - Loopback-запит — це і функція, і вразливість. Він уникає виконання важкої роботи під час користувацького запиту, але залежить від внутрішнього HTTP.
- Деякі плагіни обходять loopback і виконують завдання під час звичайних переглядів сторінок. Це може “працювати”, поки не спричинить латентність під навантаженням. Надійність проти продуктивності — вічна торгівля.
- Action Scheduler від WooCommerce став поширеним, бо WP-Cron сам по собі був грубим інструментом для важких навантажень. Він додає персистентність, ретраї і видимість — але йому все одно потрібен раннер.
- Симптом “missed schedule” часто не в логіці планування поста. Він у виконавці, який не виконав роботу в момент публікації, залишаючи пости в підвішеному стані до наступної тики.
- Сучасні кеші роблять WP-Cron менш надійним, ніж у 2008 році. Кеші зменшують звернення до PHP, а WP-Cron залежить від PHP-запитів для тригерів.
- Зворотні проксі та примусове HTTPS додали нові режими збоїв loopback. Неправильні заголовки або некоректні URL можуть спричинити внутрішні редиректи, які бачить лише cron.
Контрольні списки / покроковий план
Чекліст A: Негайна триаж (15 хвилин)
- Підтвердьте синхронізацію часу ОС (
timedatectl). Якщо не синхронізовано — виправте NTP спочатку. - Перелічіть очікувані cron-події (
wp cron event list). Якщо прострочені — виконавець зламаний. - Вручну запустіть прострочені події (
wp cron event run --due-now) щоб відновити сервіс. - Перевірте, чи WP-Cron вимкнено (
grep DISABLE_WP_CRON). - Зробіть curl до
/wp-cron.phpі зафіксуйте HTTP-код. 200 — добре; усе інше дає підказку. - Подивіться PHP-FPM та журнали веб-сервера на предмет тайм-аутів/фаталів під час спроб cron.
Чекліст B: Постійне виправлення для більшості сайтів (60–90 хвилин)
- Встановіть WP-CLI у стабільне місце (пакетний менеджер або перевірений phar), переконайтесь, що він запускається під потрібним користувачем.
- Встановіть
define('DISABLE_WP_CRON', true);уwp-config.php. - Створіть system cron job, що запускає WP-CLI щохвилини або кожні п’ять хвилин.
- Перевірте виконання через
journalctl -u cronі підтвердіть, що прострочені події очищуються. - Якщо є WooCommerce/Action Scheduler — перевірте стан черги (
wp action-scheduler list). - В будь-якому разі виключіть
/wp-cron.phpз кешу/WAF. Інші плагіни можуть все ще його викликати.
Чекліст C: Закріплення для навантажених або критичних сайтів
- Відокремте ресурси для виконання cron (виділений пул PHP-FPM або хост для раннера).
- Вимірюйте прострочені події і глибину черги; налаштуйте алерти при перевищенні порогів.
- Обмежте час виконання: налаштуйте розмір батчів, уникайте “запустити все” патернів, що спричиняють thundering herd.
- Аудитуйте заплановані завдання: приберіть дублікати, зменшіть частоту і уникайте важкої роботи всередині запитів сторінок.
- Плануйте відновлення: replay-беклог, що не розвалить фронтенд.
FAQ
1) Чому заплановані пости не публікуються, коли на сайті немає відвідувачів?
Тому що WP-Cron тригериться запитами сайту. Немає запитів — немає тригера. Використайте system cron (або job runner), щоб викликати WordPress за розкладом.
2) Чи варто вимикати WP-Cron?
Так — якщо ви замінюєте його system cron. Вимикати без заміни — це надійний спосіб зламати заплановані пости та фонові задачі.
3) Чи достатньо викликати wp-cron.php з system cron?
Може працювати, але це крихке рішення (HTTP, WAF, TLS, редиректи). WP-CLI надійніше, бо уникає всього веб-шляху.
4) Мій wp-cron.php повертає 200. Чому завдання все ще прострочені?
200 означає лише, що ендпоінт відповідає. Cron все одно може падати через PHP fatal, тайм-аути, блокування БД або недостачу воркерів. Перевірте логи і спробуйте wp cron event run --due-now.
5) Чи ламає кеш WP-Cron?
Часто так, опосередковано. Full-page кеш зменшує звернення до PHP, а це зменшує тригери WP-Cron. Також може прямо заважати, якщо /wp-cron.php кешується або піддається rate-limiting.
6) Що з WooCommerce “Scheduled Actions”?
Це Action Scheduler. Він більш видимий, ніж стандартний WP-Cron, але все одно потребує раннера. Використовуйте WP-CLI для його виконання і переконайтесь, що cadence та capacity можуть опрацювати чергу.
7) Чи можуть часові пояси спричинити “missed schedule”?
Так, особливо з DST і неправильно налаштованим часовим поясом сайту. Підтвердіть синхронізацію ОС, а потім перевірте часовий пояс WordPress через WP-CLI.
8) Як часто має працювати system cron?
Поширені варіанти: щохвилини для зайнятих сайтів із великою чергою або кожні п’ять хвилин для легших сайтів. Правильна відповідь: часто настільки, щоб “прострочені” були близькі до нуля.
9) Чи є WP-Cron ризиком для безпеки?
Не за визначенням, але відкриття wp-cron.php в інтернеті може привертати шумний трафік. Якщо ви використовуєте WP-CLI з system cron, можна зменшити зовнішню залежність.
10) Що робити, якщо я на керованому хостингу і не можу використовувати system cron?
Використайте засоби планування, які надає хост. Багато хостів мають “cron jobs” у панелі або API для планувальника. Якщо інше неможливо — викликайте /wp-cron.php по HTTP і виключіть його з WAF/кешування.
Висновок: кроки, що запобігають повторним інцидентам
Заплановані пости WordPress не виконуються з тієї ж причини, через яку більшість продакшн-систем ламаються: приховані припущення. Тут припущення — що робота по часу виконуватиметься, бо існує веб-трафік і внутрішній HTTP поводиться коректно. Це не інженерія; це надія з CMS.
Зробіть практичну річ:
- Перевірте чергу і виконайте прострочені події вручну, щоб відновити сервіс.
- Перестаньте покладатись на тригери WP-Cron. Використайте system cron з WP-CLI.
- Зміцніть шлях: виключіть
/wp-cron.phpз кешу/WAF і відокремте ресурси для cron при навантаженні. - Додайте моніторинг: прострочені події або backlog Action Scheduler. Виявляйте наступну відмову раніше за редакторів.
Зробіть планування нудним. Ваш контент-календар заслуговує кращого, ніж “хтось відвідав домашню сторінку в потрібний час”.