Przejdź do treści

Ustawienia cookies

Poniżej wypisujemy nazwa po nazwie, co ta strona zapisuje w Twojej przeglądarce. Możesz tu wrócić w każdej chwili. Odnośnik „Ustawienia cookies” stoi w stopce każdej strony.

Polityka prywatności §7
isbre
Blog · PHP i aplikacje

WP-Cron bez odwiedzających: prawdziwy harmonogram na hostingu

Zaplanowany wpis nie wychodzi, bo nikt nie wszedł na stronę. Dlaczego WP-Cron czeka na odwiedzających i jak ustawić prawdziwy cron, z logiem przed i po.

· Zespół administratorów Isbre

Sprawdzone w panelu:

  • WordPress
  • WP-Cron
  • cron
  • WP-CLI

Wpis zaplanowany na 9:00 o 9:30 dalej ma status „Zaplanowany”. Wtyczka kopii zapasowych od rana nic nie zrobiła. Czy to wina hostingu? Zwykle nie: WordPress czeka, aż ktoś wejdzie na stronę. Zmierzyliśmy to czekanie na testowej instalacji i pokazujemy log przed i po przejściu na prawdziwy harmonogram.

Dlaczego WP-Cron czeka na odwiedzającego

WP-Cron to harmonogram zaszyty w WordPressie, a nie cron systemu. Według dokumentacji WordPressa sprawdza przy każdym załadowaniu strony listę zaplanowanych zadań i wykonuje te, którym minął termin. Nie działa w tle, tylko przy wizycie. Dokumentacja ostrzega, że zadanie zaplanowane na 14:00 spóźni się, jeśli pierwsza wizyta przyjdzie dopiero o 17:00.

Z WP-Crona korzysta rdzeń WordPressa (sprawdzanie aktualizacji, publikowanie zaplanowanych wpisów) i wiele wtyczek. Zaległe zadania nie przepadają, tylko czekają w kolejce na najbliższą wizytę. Dokumentacja ujmuje to tak, że zadanie na pewno się wykona, ale nie wiadomo kiedy. Na stronie firmowej, którą w nocy nikt nie odwiedza, to „kiedyś” potrafi trwać wiele godzin.

Log przed: 25 minut bez odwiedzin

Postawiliśmy czystego WordPressa 7.1.4 kreatorem na serwerze testowym, w osobnym kontenerze, z WP-Cronem w ustawieniu domyślnym. Wszystkie czasy poniżej są w UTC, bo serwer chodzi w UTC.

Zaplanowaliśmy dwie rzeczy. Wpis miał wyjść o 20:34:40. Zdarzenie testowe miało się wykonywać co 5 minut od 20:29:40 i dopisywać do pliku chwilę swojego uruchomienia. Potem przez 25 minut nikt nie wszedł na stronę, a dziennik dostępu Apache zostawał pusty. Stan po tym czasie:

20:49:44 UTC

$ wp cron event list --fields=hook,next_run_gmt
hook                   next_run_gmt
t774_zdarzenie         2026-11-05 20:29:40
publish_future_post    2026-11-05 20:34:40

$ wp post get 5 --fields=ID,post_status,post_date_gmt
ID               5
post_date_gmt    2026-11-05 20:34:40
post_status      future

plik z logiem zdarzenia: nie istnieje, zdarzenie nie wykonało się ani razu
żądania w dzienniku dostępu Apache od początku pomiaru: 0

Oba terminy minęły: zdarzenia o 20 minut, wpisu o 15. Potem weszliśmy na stronę jednym zwykłym GET /:

20:49:46 UTC   GET / -> 200

dziennik dostępu Apache:
127.0.0.1 - - [05/Nov/2026:20:49:46 +0000] "POST /wp-cron.php?doing_wp_cron=1793911786.40… HTTP/1.1" 200 315 "-" "WordPress/7.1.4; http://t774.isbre.pl"
127.0.0.1 - - [05/Nov/2026:20:49:46 +0000] "GET / HTTP/1.1" 200 69609 "-" "curl/8.14.1"

20:49:54 UTC

post_status      publish
t774_zdarzenie   następny termin 2026-11-05 20:54:40

plik z logiem zdarzenia:
2026-11-05 20:49:46 UTC  t774_zdarzenie wykonane

Ta jedna wizyta wystarczyła. WordPress sam zawołał wp-cron.php (to POST z agentem „WordPress/7.1.4” w tej samej sekundzie), opublikował wpis z 15-minutowym spóźnieniem i wykonał zdarzenie. Pięć terminów tego zdarzenia (20:29:40, 20:34:40, 20:39:40, 20:44:40 i 20:49:40) skończyło się jednym wykonaniem, a następny termin ustawił na 20:54:40. Gdyby wizyta przyszła rano, spóźnienie wyniosłoby godziny, nie minuty.

Krok 1: wyłącz uruchamianie z ruchu

W wp-config.php dopisz nad wierszem „That’s all, stop editing!” jedną linię:

define( 'DISABLE_WP_CRON', true );

Możesz też zrobić to z WP-CLI przez SSH: wp config set DISABLE_WP_CRON true --raw --type=constant. Polecenie wstawia stałą we właściwym miejscu pliku, nad wierszem „stop editing”. Dokumentacja WordPressa mówi, że ta stała wyłącza cron w całości, więc zrób krok 2 od razu. Bez niego nic się nie wykona, nawet przy odwiedzinach.

Krok 2: dodaj zadanie w panelu

W panelu otwórz usługę, wejdź w „Zadania cykliczne (cron)”, kliknij „Dodaj zadanie” i wypełnij dwa pola.

Harmonogram: */5 * * * *, czyli co 5 minut. Gotowy przycisk „Co 15 minut” też się nada, jeśli kwadrans opóźnienia nie przeszkadza.

Komenda (jedna linia, w miejsce twojadomena.pl wpisz swoją domenę). Taką komendę podpowiada też panel w polu „Komenda”:

cd ~/domains/twojadomena.pl/public_html && wp cron event run --due-now >> ~/logs/wp-cron.log 2>&1

Każdy kawałek ma swoją robotę:

  • cd ~/domains/twojadomena.pl/public_html wchodzi do katalogu, w którym leży WordPress.
  • wp cron event run --due-now to polecenie WP-CLI, które wykonuje zdarzenia z minionym terminem. Według dokumentacji WP-CLI pilnuje przy tym blokady doing_cron, więc dwa przebiegi nie nałożą się na siebie. Programy wp, composer i php działają w zadaniu bez pełnej ścieżki, tak jak w SSH.
  • >> ~/logs/wp-cron.log 2>&1 dopisuje wynik do pliku w katalogu logs, w którym leży też dziennik błędów PHP. To jedyne miejsce, w którym zobaczysz wynik zadania: crontab ma ustawione MAILTO="", więc nic nie przychodzi pocztą.

Chcesz mieć w logu godzinę każdego przebiegu? Owiń polecenie w nawias z date:

cd ~/domains/twojadomena.pl/public_html && (date '+\%F \%T'; wp cron event run --due-now) >> ~/logs/wp-cron.log 2>&1

Znak % ma w crontabie znaczenie specjalne, dlatego w date stoi przed nim ukośnik. W takiej postaci zadanie działało w pomiarze niżej.

Dokumentacja WordPressa proponuje prostszy wariant: cron wywołujący wget --delete-after adres-twojej-strony/wp-cron.php. Działa, ale idzie przez serwer WWW, a więc podlega limitowi czasu żądania. WP-CLI uruchamia PHP z konsoli, które nie ma limitu czasu wykonania (zmierzyliśmy max_execution_time równe 0), więc długie zadanie wtyczki nie zostanie ucięte w połowie.

Polecenie php w konsoli nie musi być wersją, na której chodzi strona. Jeśli zadanie ma użyć tej samej, wpisz ją wprost: php8.4 /usr/local/bin/wp cron event run --due-now.

Log po: 17 minut bez odwiedzin

Ten sam układ, ten sam WordPress, tym razem z krokami 1 i 2: stała DISABLE_WP_CRON w wp-config.php (WP-CLI wstawiło ją nad wierszem „stop editing”) i zadanie */5 * * * * z komendą z kroku 2, w wariancie z date, założone tym samym poleceniem serwera, które woła panel. Zdarzenie testowe z poprzedniego pomiaru zostało. Nowy wpis zaplanowaliśmy na 20:58:06. Przez kolejne 17 minut nikt nie wszedł na stronę, a dziennik dostępu Apache dalej miał zero żądań. Tak wyglądał ~/logs/wp-cron.log:

2026-11-05 20:55:01
Executed the cron event 'do_pings' in 0.439s.
Executed the cron event 't774_zdarzenie' in 0.008s.
Success: Executed a total of 2 cron events.
2026-11-05 21:00:01
Executed the cron event 'publish_future_post' in 0.076s.
Executed the cron event 't774_zdarzenie' in 0.011s.
Success: Executed a total of 2 cron events.
2026-11-05 21:05:01
Executed the cron event 'do_pings' in 0.444s.
Executed the cron event 't774_zdarzenie' in 0.019s.
Success: Executed a total of 2 cron events.

A tak plik zdarzenia testowego, do którego dopisuje się jego własny kod:

2026-11-05 20:49:46 UTC  t774_zdarzenie wykonane
2026-11-05 20:55:03 UTC  t774_zdarzenie wykonane
2026-11-05 21:00:02 UTC  t774_zdarzenie wykonane
2026-11-05 21:05:02 UTC  t774_zdarzenie wykonane

Pierwsza linia tego pliku pochodzi jeszcze z pomiaru „przed”. Przebiegi zadania ruszają o równych porach, co 5 minut: 20:55:01, 21:00:01 i 21:05:01. Wpis z terminem 20:58:06 wyszedł w przebiegu z 21:00:01, 1 minutę i 55 sekund po czasie, a dziennik dostępu Apache dalej nie miał ani jednego żądania. Zdarzenie testowe, które w pomiarze „przed” stało 20 minut nieruszone, wykonywało się teraz w pierwszym przebiegu po terminie (20:54:40, 20:59:40, 21:04:40), od 22 do 23 sekund po nim. Spóźnienie zależy więc od tego, jak termin trafia w przebieg: raz było to 22 sekundy, raz prawie 2 minuty, a górną granicą jest odstęp zadania. Nazwa do_pings w logu to zdarzenie rdzenia, które WordPress dokłada po opublikowaniu wpisu: po publikacji z 20:49 poszło w przebiegu z 20:55, po publikacji z 21:00 w przebiegu z 21:05.

Pozostaje pytanie, czy wizyta nadal cokolwiek uruchamia. O 21:07:10 dołożyliśmy zdarzenie jednorazowe z terminem „teraz” i od razu weszliśmy na stronę:

21:07:12 UTC   GET / -> 200

dziennik dostępu Apache od początku pomiaru:
127.0.0.1 - - [05/Nov/2026:21:07:12 +0000] "GET / HTTP/1.1" 200 71438 "-" "curl/8.14.1"

21:07:22 UTC

hook                next_run_gmt
t774_jednorazowe    2026-11-05 21:07:11

21:10:01 UTC, wp-cron.log:
Executed the cron event 't774_jednorazowe' in 0.006s.

W dzienniku nie ma żadnego POST /wp-cron.php. Zdarzenie czekało zaległe, aż ruszył przebieg zadania o 21:10:01. Po kroku 1 wizyty niczego już nie uruchamiają, robi to wyłącznie zadanie z panelu.

Na co uważać

  • Spóźnienie ma górną granicę. Granicą jest odstęp zadania: przy */5 zdarzenie rusza najpóźniej 5 minut po terminie, przy przycisku „Co 15 minut” kwadrans po nim. Wpis zaplanowany na 8:00 wyjdzie w pierwszym przebiegu po 8:00.
  • Jedna instalacja, jedno zadanie. WP-CLI działa na katalogu, do którego wejdzie cd, więc drugi WordPress (inna domena albo podkatalog) potrzebuje własnego zadania. Limit zadań zależy od pakietu: od 3 w Start do 50 w Max.
  • Godziny w panelu to UTC. Panel pisze to przy polu „Harmonogram” i przy gotowych przyciskach („Codziennie o 6:00 UTC”). Zadanie na 0 6 * * * ruszy o 7:00 zimą i o 8:00 latem, a „następnie” na liście zadań też jest w UTC. Przy */5 nie ma to znaczenia, przy zadaniu dziennym ma.
  • Log nie obraca się sam. Dziennik błędów PHP w katalogu logs platforma obraca, ale wp-cron.log już nie. Przebieg bez zdarzeń dopisuje do niego około 64 bajtów, a przebieg z dwoma zdarzeniami około 160. Przy 288 przebiegach na dobę to od 18 do 46 KB. Od czasu do czasu skasuj plik.

Strona z izolacją ma własny harmonogram. Wybierz ją w polu „Gdzie uruchomić”, bo zadanie konta głównego nie ma dostępu do jej plików. Zadanie wykona się jako użytkownik tej strony, bez katalogu domowego, więc wszystkie ścieżki podaj w całości. Katalog logs konta jest dla takiej strony zamknięty do zapisu, dlatego log trafia do katalogu tmp strony. Panel podpowiada to po wybraniu strony, a login zamień na login konta:

cd /home/login/domains/twojadomena.pl/public_html && wp cron event run --due-now >> /home/login/domains/twojadomena.pl/tmp/wp-cron.log 2>&1

Co sprawdzić dziś

  1. Zajrzyj do wp-config.php i do panelu. Stała DISABLE_WP_CRON bez zadania w panelu znaczy, że żadne zdarzenie się nie wykona. Zadanie bez stałej znaczy, że WordPress dalej uruchamia cron także przy wizytach.
  2. Zaplanuj wpis na najbliższe 10 minut i sprawdź w kokpicie po kwadransie. Przy włączonym kroku 1 wizyta w kokpicie niczego nie uruchamia, więc jeśli wpis wyszedł, zrobiło to zadanie.
  3. Po godzinie otwórz ~/logs/wp-cron.log, przez SFTP albo SSH. Powinien mieć wpis co 5 minut, a w nim „Success: Executed a total of N cron events.”.
  4. Jeśli po kwadransie nie ma pliku wp-cron.log, cd nie trafił w katalog WordPressa. Błąd cd nie trafia do logu, więc sprawdź ścieżkę z zadania na liście i w SFTP.

Źródła

Strony odczytane 5 listopada 2026 (mogą się zmienić):

  • WordPress, Cron: developer.wordpress.org/plugins/cron/
  • WordPress, Hooking WP-Cron Into the System Task Scheduler: developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/
  • WordPress, Editing wp-config.php: developer.wordpress.org/advanced-administration/wordpress/wp-config/
  • WP-CLI, wp cron event run: developer.wordpress.org/cli/commands/cron/event/run/

Pytania do tego wpisu? Napisz na bok@isbre.pl. Wszystkie wpisy: blog.