Dla programistów
Wyrażenie cron: jak zapisać harmonogram i nie pomylić godzin
Wyrażenie cron wygląda prosto, a pomyłka wychodzi dopiero po tygodniu: zadanie uruchamia się za często, o złej godzinie albo wcale. Sprawdź zapis w narzędziu, zanim wkleisz go do konfiguracji.
- Redakcja HNarzędzi
- Publikacja:
Wyrażenie cron to pięć pól oddzielonych spacjami: minuta, godzina, dzień miesiąca, miesiąc i dzień tygodnia. Najwięcej problemów robią trzy rzeczy: dzień miesiąca łączony z dniem tygodnia, strefa czasowa serwera i zmiana czasu. Dalej każdy z nich jest sprawdzony w generatorze cron, który wypisuje opis słowny i listę 10 najbliższych uruchomień.
Wszystkie pomiary w tym poradniku zrobiliśmy w Chromium sterowanym Playwrightem, ze strefą Europe/Warsaw i ustawioną na sztywno datą: niedziela 11 października 2026, 10:00 czasu warszawskiego. Skrypty leżą w repozytorium (scripts/guide-fixtures/cron-expression-examples*.mjs). Ty zobaczysz listę liczoną od swojej bieżącej daty.
Pięć pól i ich zakresy
| Pole | Zakres | Uwagi |
|---|---|---|
| Minuta | 0-59 | |
| Godzina | 0-23 | doba w formacie 24-godzinnym |
| Dzień miesiąca | 1-31 | |
| Miesiąc | 1-12 lub JAN-DEC | |
| Dzień tygodnia | 0-7 lub SUN-SAT | 0 i 7 to niedziela |
Zakresy minut, godzin, dni i miesięcy są takie same w specyfikacji POSIX. POSIX przyjmuje dla dnia tygodnia tylko 0-6 (0 to niedziela), a 0-7 i nazwy dni pochodzą z crontaba Vixie, zapisanego w man crontab(5) dla pakietu cron w Debianie (sprawdzone w październiku 2026).
W każdym polu możesz użyć czterech znaków:
*- każda wartość,,- lista (1,15),-- zakres (1-5),/- krok (*/15to co 15 jednostek od początku zakresu).
Jeśli używasz nazw (MON-FRI), zapis jest czytelniejszy niż cyfry i nie zależy od tego, czy system liczy niedzielę jako 0, czy 7.
Typowe zadania sprawdzone w narzędziu
Każde wyrażenie wpisaliśmy w generator i zapisaliśmy to, co pokazał interfejs. Daty w ostatniej kolumnie przepisaliśmy skrótowo, narzędzie wypisuje je pełnym tekstem z dniem tygodnia.
| Zadanie | Wyrażenie | Opis w narzędziu | Pierwsze uruchomienia |
|---|---|---|---|
| codziennie o 3:00 | 0 3 * * * |
O 03:00 | 12.10, 13.10, 14.10, … każdego dnia o 03:00 |
| dni robocze o 9:00 | 0 9 * * 1-5 |
O 09:00, od poniedziałku do piątku | pn 12.10, wt 13.10, śr 14.10, czw 15.10, pt 16.10, potem pn 19.10 |
| co 15 minut | */15 * * * * |
Co 15 minut | 11.10 o 10:15, 10:30, 10:45, 11:00 |
| pierwszego dnia miesiąca | 0 0 1 * * |
O 00:00, 1. dnia miesiąca | 1.11.2026, 1.12.2026, 1.01.2027, 1.02.2027 |
Kilka uwag do tej tabeli:
*/15zaczyna liczyć od początku zakresu, więc uruchomienia wypadają w minutach 0, 15, 30 i 45. Pierwsze po 10:00 jest o 10:15, bo narzędzie nie liczy uruchomienia, które właśnie minęło.1-5w polu dnia tygodnia to poniedziałek-piątek. Cron nie zna świąt, więc w dzień wolny od pracy zadanie też ruszy.- Dzień miesiąca
31pomija miesiące, które go nie mają. Dla0 0 31 * *narzędzie pokazało 31.10.2026, 31.12.2026, 31.01.2027, 31.03.2027 i 31.05.2027. Listopad, luty i kwiecień wypadły z listy. 0 0 29 2 *uruchomi się tylko w lata przestępne: pierwsze dwa terminy to 29.02.2028 i 29.02.2032.0 0 30 2 *nie ma żadnego terminu, a narzędzie pisze, że wyrażenie nie uruchomi się w ciągu najbliższych 8 lat.
Zapis, który się nie zgadza z zakresem, narzędzie odrzuca z komunikatem:
| Wpisane | Komunikat narzędzia |
|---|---|
0 0 * * |
Wyrażenie ma 4 pól, a powinno mieć 5 (…) lub 6 (z sekundami na początku) |
61 * * * * |
Pole „Minuta”: wartość „61” jest poza zakresem 0-59 |
0 24 * * * |
Pole „Godzina”: wartość „24” jest poza zakresem 0-23 |
*/0 * * * * |
Pole „Minuta”: niepoprawny krok w „*/0” |
Dzień miesiąca i dzień tygodnia: warunek „lub”
Najczęstsza niespodzianka: gdy wpiszesz konkretną wartość i w polu dnia miesiąca, i w polu dnia tygodnia, cron nie szuka dnia, który spełnia oba warunki. Uruchamia zadanie, gdy pasuje którykolwiek z nich. Tak opisuje to man crontab(5) Vixie: jeśli oba pola są ograniczone (nie zaczynają się od gwiazdki), polecenie ruszy, gdy dopasuje się któreś z nich. W POSIX jest to samo (sekcja o crontab).
Przykład: ktoś chce uruchamiać zadanie w piątek trzynastego, więc zapisuje 0 0 13 * 5. Porównaj trzy wyrażenia w narzędziu:
| Wyrażenie | Pierwsze uruchomienia od 11.10.2026 |
|---|---|
0 0 13 * * |
wt 13.10, pt 13.11, nd 13.12, śr 13.01.2027 |
0 0 * * 5 |
pt 16.10, 23.10, 30.10, 6.11, 13.11, 20.11 |
0 0 13 * 5 |
wt 13.10, potem piątki: 16.10, 23.10, 30.10, 6.11, 13.11 |
Trzecie wyrażenie to suma dwóch pierwszych: dostajesz każde 13. i każdy piątek. W opisie narzędzie pisze „O 00:00, 13. dnia miesiąca, lub w piątek” i pokazuje ostrzeżenie o warunku „lub”.

Jak zostawić tylko jeden warunek:
- Wpisz gwiazdkę w polu, którego nie potrzebujesz:
0 0 * * 5to wszystkie piątki,0 0 13 * *to wszystkie 13. dni. - Gdy naprawdę potrzebujesz „13., ale tylko jeśli to piątek”, ustaw harmonogram na piątki i sprawdź datę w samym zadaniu (skrypt kończy działanie, gdy dzień miesiąca jest inny niż 13).
Pole zaczynające się od gwiazdki nie jest ograniczone, także gdy ma krok, np. */2. Wtedy oba warunki muszą być spełnione naraz: 0 0 */2 * 5 uruchomi zadanie tylko w piątki, które wypadają w nieparzysty dzień miesiąca. Narzędzie pokazało 23.10, 13.11 i 27.11.2026, opis „O 00:00, co 2 dni, tylko w piątek” i nie wyświetliło ostrzeżenia o warunku „lub”.
Strefy czasowe i zmiana czasu
Wyrażenie cron nie zawiera strefy. O tym, co znaczy „9:00”, decyduje program, który je wykonuje:
- Zwykły crontab używa strefy serwera. Cronie, jedna z implementacji crona w dystrybucjach Linuksa, ma też zmienną
CRON_TZustawianą dla jednej tabeli (man crontab(5) cronie). - GitHub Actions liczy w UTC. Dokumentacja podaje: „By default, scheduled workflows run in UTC” (GitHub Docs, zdarzenie schedule).
- Kubernetes CronJob bez pola
.spec.timeZoneliczy w strefie kontrolera (kube-controller-manager). PoletimeZonema status stabilnego od wersji 1.27, a wpisywanieCRON_TZlubTZwschedulejest odrzucane z błędem walidacji (dokumentacja Kubernetes).
Narzędzie liczy terminy w strefie przeglądarki i pod listą wypisuje jej nazwę (u nas „Europe/Warsaw”). Jeśli serwer działa w innej strefie, lista nie będzie się zgadzać z tym, co zobaczysz w logach.
GitHub Actions: 8:00 w Polsce to dwa różne wyrażenia
Zadanie, które ma ruszać o 8:00 czasu polskiego, w Actions trzeba zapisać w UTC, więc dwa razy w roku trzeba je zmienić. Zmierzyliśmy to w konwerterze Unix timestamp, wpisując 8:00 czasu lokalnego (Europe/Warsaw):
| Data lokalna, 8:00 | Czas w UTC (ISO 8601 w narzędziu) | Wyrażenie dla Actions |
|---|---|---|
| 15 lipca 2026 | 2026-07-15T06:00:00.000Z | 0 6 * * * |
| 15 stycznia 2027 | 2027-01-15T07:00:00.000Z | 0 7 * * * |
Jedno wyrażenie nie obsłuży obu pór roku. Rozwiązania są dwa: zaakceptować przesunięcie o godzinę albo zapisać dwa harmonogramy i w pierwszym kroku zadania sprawdzać lokalną godzinę. Przy okazji dokumentacja GitHuba zastrzega, że najkrótszy odstęp to 5 minut, uruchomienie może się opóźnić przy dużym obciążeniu, a harmonogram działa tylko na domyślnej gałęzi.
Zmiana czasu o 2:30 w nocy
W Polsce czas przestawia się dwa razy w roku. W 2027 roku wiosną zegar skacze z 2:00 na 3:00 w niedzielę 28 marca, a w 2026 roku jesienią cofa się z 3:00 na 2:00 w niedzielę 25 października. Wyrażenie 30 2 * * * trafia wtedy w godzinę, której nie ma (wiosna) albo w taką, która wystąpi dwa razy (jesień).
Co o tym mówi dokumentacja (sprawdzone w październiku 2026): man crontab(5) cronie opisuje samą regułę dopasowania: nieistniejące godziny, takie jak „brakujące” przy zmianie czasu, nigdy się nie dopasują, a godziny powtórzone dopasują się dwa razy. Ten sam projekt w man cron(8) opisuje jednak, że demon obsługuje zmiany czasu mniejsze niż 3 godziny w szczególny sposób, ale tylko dla zadań o stałej godzinie i zadań uruchamianych rzadziej niż co godzinę. Po przestawieniu zegara do przodu uruchamia od razu zadania z pominiętej godziny, a po cofnięciu nie uruchamia ich drugi raz. Zadania częstsze, np. */15, są planowane zwyczajnie. Strona Vixie w Debianie nie opisuje tego przypadku w ogóle, więc zachowanie zależy od implementacji i wersji crona. Najpewniej sprawdzić je w dokumentacji własnego systemu.
Co pokazuje narzędzie. Wiosna, data bieżąca ustawiona na sobotę 20 marca 2027, 12:00, wyrażenie 30 2 * * *:
| Nr | Uruchomienie |
|---|---|
| 1-7 | niedziela 21.03 do sobota 27.03, każdego dnia 02:30 |
| 8 | poniedziałek 29.03, 02:30 |
| 9 | wtorek 30.03, 02:30 |
| 10 | środa 31.03, 02:30 |
Niedzieli 28 marca nie ma na liście: narzędzie pomija godzinę, która nie istnieje. 0 2 * * * zachowuje się tak samo. Wyrażenie 0 3 * * * w tym samym teście uruchamia się 28 marca o 03:00, jak w każdy inny dzień.

Jesień, data bieżąca wtorek 20 października 2026, 12:00, wyrażenie 30 2 * * *: lista ma jedno uruchomienie 25 października o 02:30 (między 24 a 26 października). Pod tą datą narzędzie dopisuje: „Tego dnia ta godzina występuje dwa razy, bo zegar cofa się o godzinę. Część implementacji cron uruchomi zadanie dwukrotnie.” Wpis zostaje jeden, bo to, czy zadanie ruszy raz czy dwa razy, zależy od programu, który je wykonuje. Wyrażenie 30 3 * * * w tym samym teście nie ma adnotacji: 3:30 występuje tego dnia raz.
Wniosek praktyczny: w strefie Europe/Warsaw nie planuj w oknie 2:00-2:59 zadań, które muszą ruszyć dokładnie raz. Godzina 3:00 istnieje w obu dniach zmiany czasu. Ten wybór nie dotyczy innych stref: w USA zmiana czasu następuje w innych dniach i o innej godzinie. Alternatywa to serwer ustawiony na UTC, bo w UTC zmiany czasu nie ma.
Różnice między crontabem, Quartz, Actions i Kubernetes
| System | Pola | Dzień tygodnia | Strefa | Źródło |
|---|---|---|---|---|
| crontab (Vixie, cronie) | 5 | 0-7 lub SUN-SAT, 0 i 7 to niedziela | strefa serwera, w cronie CRON_TZ |
man crontab(5) |
GitHub Actions schedule |
5 (składnia POSIX) | jak w POSIX | zawsze UTC | GitHub Docs |
| Kubernetes CronJob | 5 | 0-6 lub sun-sat | strefa kontrolera albo .spec.timeZone |
Kubernetes |
| Quartz | 6 lub 7 (sekundy na początku, rok opcjonalnie na końcu) | 1-7 lub SUN-SAT | ustawiana w harmonogramie | CronTrigger Quartz |
Źródła z tej tabeli sprawdziliśmy w październiku 2026. Z dokumentacji Quartza warto zapamiętać dwie rzeczy. Po pierwsze, w jednym z pól dni trzeba wpisać ? („bez określonej wartości”), bo jednoczesne podanie dnia miesiąca i dnia tygodnia nie jest w pełni wspierane. Po drugie, w Quartz dni tygodnia numeruje się od 1 do 7, a przykład z dokumentacji pokazuje, że 6#3 to trzeci piątek miesiąca. W Kubernetes ? znaczy to samo co *.
Narzędzie rozumie 6 pól z sekundami na początku i znaki L, W i #, ale wypisuje przy nich uwagę, że crontab, Actions i Kubernetes ich nie obsługują. Wyrażenie z 6 polami i ? w polu dnia miesiąca albo dnia tygodnia narzędzie traktuje jako zapis Quartz i liczy w nim dni tygodnia od 1 (niedziela) do 7 (sobota). Gdy w polu dnia tygodnia są cyfry, wyświetla o tym osobną uwagę, a w tabeli pól podaje zakres 1-7. Sprawdziliśmy pięć zapisów w stylu Quartz:
| Wyrażenie | Opis w narzędziu | Pierwsze uruchomienia |
|---|---|---|
0 30 9 * * 1-5 |
O 09:30, od poniedziałku do piątku | pn 12.10.2026 o 09:30:00, potem kolejne dni robocze |
0 0 9 ? * MON-FRI |
O 09:00, od poniedziałku do piątku | pn 12.10.2026 o 09:00:00 |
0 0 9 L * ? |
O 09:00, ostatni dzień miesiąca | 31.10, 30.11, 31.12.2026, 31.01.2027 i 28.02.2027, o 09:00:00 |
0 0 9 ? * 6#3 |
O 09:00, trzeci piątek miesiąca | pt 16.10, 20.11, 18.12.2026, o 09:00:00 |
0 0 9 ? * 5 |
O 09:00, tylko w czwartek | czw 15.10, 22.10, 29.10.2026, o 09:00:00 |
Uwaga na pierwszy wiersz. Wyrażenie 0 30 9 * * 1-5 nie ma ?, więc narzędzie liczy w nim dni jak crontab: 1-5 to poniedziałek-piątek. Quartz nie przyjąłby tego zapisu, bo wymaga ? w jednym z pól dni, a w jego numeracji 1-5 to niedziela-czwartek. Jeśli nie wiesz, jak Twój harmonogram numeruje dni, wpisz nazwy (MON-FRI). Znaczą to samo w crontabie i w Quartz.
Czego narzędzie nie robi
- Nie zna Twojego serwera: liczy w strefie przeglądarki i nie sprawdza, czy harmonogram w Actions, Kubernetes lub crontabie będzie w tej samej strefie.
- Nie pokazuje przesunięcia UTC przy terminach. Godzinę, która jesienią wystąpi dwa razy, pokazuje jako jeden wpis z adnotacją, a nieistniejącą godzinę wiosną pomija bez komentarza.
- Nie obsługuje pola roku (7 pól Quartz) ani
@reboot; w obu przypadkach zwraca komunikat. - Szuka terminów 8 lat do przodu.
- Opis słowny jest tłumaczeniem biblioteki cronstrue, poprawianym w polskiej odmianie, więc przy nietypowych zapisach zdarzają się niezgrabne sformułowania. Rozstrzygaj według listy uruchomień, nie samego opisu.
Do szybkiego sprawdzenia, jak 8:00 czy 3:00 wygląda w UTC, użyj konwertera Unix timestamp. Gotowy zapis wpisz do generatora cron i porównaj z listą uruchomień, zanim wkleisz go do konfiguracji.