Wyrażenie cron to pięć pól oddzielonych spacjami: minuta, godzina, dzień miesiąca, miesiąc i dzień tygodnia, a po nich polecenie do wykonania. 30 2 * * * oznacza „codziennie o 02:30”. Każde pole przyjmuje liczbę, * dla „każdej wartości”, listę rozdzieloną przecinkami, zakres z myślnikiem albo krok z ukośnikiem. Większość problemów z cronem nie wynika z samej składni, tylko z jej otoczenia: strefy czasowej serwera, reguły łączenia dnia miesiąca z dniem tygodnia i okrojonego środowiska, w którym cron uruchamia polecenia.
W tym poradniku omawiamy każde pole i każdy znak specjalny, podajemy tabelę gotowych wyrażeń i pokazujemy błędy, przez które zadania uruchamiają się o złej porze albo wcale.
Pięć pól
Każdy wiersz w crontabie użytkownika ma ten sam układ:
┌───────────── minuta (0–59)
│ ┌─────────── godzina (0–23)
│ │ ┌───────── dzień miesiąca (1–31)
│ │ │ ┌─────── miesiąc (1–12 lub JAN–DEC)
│ │ │ │ ┌───── dzień tygodnia (0–7 lub SUN–SAT, 0 i 7 to niedziela)
│ │ │ │ │
* * * * * /path/to/command
Zadanie uruchamia się, gdy bieżąca minuta pasuje do wszystkich pól, z jednym wyjątkiem dotyczącym dwóch pól dni, opisanym niżej. Miesiące i dni tygodnia można zapisywać trzyliterowymi nazwami angielskimi, bez względu na wielkość liter.
Własny crontab edytujesz poleceniem crontab -e, a wyświetlasz poleceniem crontab -l. Pliki systemowe, /etc/crontab i pliki w /etc/cron.d/, mają szóste pole między harmonogramem a poleceniem: użytkownika, jako który zadanie się uruchamia. Skopiowanie wiersza z jednego formatu do drugiego bez dodania lub usunięcia tego pola to klasyczny sposób na zepsucie zadania.
Znaki specjalne
ZnakZnaczeniePrzykładWynik*Każda wartość* * * * *Co minutę,Lista wartości0 8,12,18 * * *O 08:00, 12:00 i 18:00-Zakres0 9 * * 1-5O 09:00 od poniedziałku do piątku/Krok*/15 * * * *W minutach 0, 15, 30 i 45
Jak naprawdę działają kroki
Krok dzieli zakres pola, zaczynając od jego pierwszej wartości. */15 w polu minut oznacza 0, 15, 30 i 45. Krok można też zastosować do zakresu: 0-30/10 oznacza minuty 0, 10, 20 i 30.
Kroki zaczynają się od nowa na granicy pola. */7 w polu minut uruchamia zadanie w minutach 0, 7, 14 i tak dalej aż do 56, a potem znowu w minucie 0, więc ostatnia przerwa w każdej godzinie trwa tylko cztery minuty. */7 to nie „co siedem minut”. Klasyczny cron nie wyrazi też w jednym wierszu „co 90 minut”; potrzebujesz dwóch wierszy albo sprawdzenia wewnątrz skryptu.
Składnia spoza klasycznego crona
W dokumentacji możesz trafić na L (ostatni), W (najbliższy dzień roboczy), # (n-ty dzień tygodnia w miesiącu) i ?. Pochodzą z Quartz oraz niektórych harmonogramów chmurowych i CI, a nie z crona na serwerze z Linuksem, a część tych harmonogramów dodaje też na początku pole sekund. Wiele implementacji crona odrzuca również zakresy nazw, więc pisz 1-5 zamiast MON-FRI.
Skróty takie jak @daily
Większość implementacji crona w Linuksie przyjmuje nazwany skrót zamiast pięciu pól:
@reboot: raz, gdy demon crona startuje po uruchomieniu systemu.
@yearly lub @annually: 0 0 1 1 *, o północy 1 stycznia.
@monthly: 0 0 1 * *, o północy pierwszego dnia miesiąca.
@weekly: 0 0 * * 0, o północy z soboty na niedzielę.
@daily lub @midnight: 0 0 * * *.
@hourly: 0 * * * *.
Wszystkie skróty odpalają się o pełnej godzinie albo o północy, więc na obciążonym serwerze każde zadanie walczy o te same zasoby o 00:00. Pełne wyrażenie z nietypową minutą, na przykład 17 0 * * *, rozkłada obciążenie.
Gotowe wyrażenia cron
WyrażenieUruchamia się* * * * *Co minutę*/5 * * * *Co 5 minut*/15 * * * *Co 15 minut0 * * * *Co godzinę, o pełnej godzinie15 */2 * * *W 15. minucie co drugiej godziny: 00:15, 02:15, 04:15 i tak dalej0 9-17 * * 1-5Co godzinę od 09:00 do 17:00, od poniedziałku do piątku*/10 8-18 * * 1-5Co 10 minut w godzinach pracy w dni robocze30 2 * * *Codziennie o 02:300 8,12,18 * * *Trzy razy dziennie: 08:00, 12:00 i 18:000 6 * * 1W każdy poniedziałek o 06:000 22 * * 5W każdy piątek o 22:000 0 * * 0W każdą niedzielę o północy0 3 1 * *Pierwszego dnia każdego miesiąca o 03:000 4 1,15 * *1. i 15. dnia każdego miesiąca o 04:000 5 1 */3 *Co kwartał: 1 stycznia, kwietnia, lipca i października o 05:000 0 1 1 *Raz w roku, o północy 1 stycznia@rebootRaz po każdym uruchomieniu systemu
Częsty błąd w tej grupie to * */2 * * *. To wyrażenie nie uruchamia zadania co dwie godziny. Uruchamia je co minutę w co drugiej godzinie, sześćdziesiąt razy z rzędu. Gdy ograniczasz godzinę, zawsze ustaw też minutę.
Dzień miesiąca i dzień tygodnia: reguła LUB
Wszystkie pola łączy się warunkiem I, z wyjątkiem dwóch pól dni. Gdy ograniczony jest zarówno dzień miesiąca, jak i dzień tygodnia, czyli żadne z nich nie jest *, zadanie uruchamia się, gdy pasuje którekolwiek z nich. Dlatego ten wiersz:
0 9 1 * 1 /usr/local/bin/report.sh
uruchamia się o 09:00 pierwszego dnia każdego miesiąca oraz o 09:00 w każdy poniedziałek, a nie tylko w poniedziałki wypadające pierwszego. Z tego samego powodu 0 9 1-7 * 1 nie oznacza „pierwszego poniedziałku miesiąca”; zadanie uruchomi się w dniach od 1 do 7 i w każdy poniedziałek. Aby trafić w pierwszy poniedziałek, zaplanuj dni od 1 do 7 i sprawdź dzień tygodnia w poleceniu. Zwróć uwagę na poprzedzony ukośnikiem znak procentu, o którym piszemy niżej:
0 9 1-7 * * [ „$(date +\%u)” = „1” ] && /usr/local/bin/report.sh
Klasyczny cron nie zna też „ostatniego dnia miesiąca”. Z GNU date zaplanuj dni od 28 do 31 i uruchamiaj zadanie tylko wtedy, gdy jutro jest pierwszy:
0 23 28-31 * * [ „$(date -d tomorrow +\%d)” = „01” ] && /usr/local/bin/month-end.sh
Strefy czasowe i zmiana czasu
Cron używa strefy czasowej systemu, na którym działa. Zadanie napisane pod czas sztokholmski na serwerze ustawionym na UTC startuje godzinę lub dwie wcześniej albo później, zależnie od pory roku. Zanim wybierzesz harmonogram, sprawdź strefę serwera poleceniem timedatectl lub date. Tej samej strefy użyj przy monitorowaniu zadania: monitory crona w Uptimecheck przyjmują harmonogram z crontaba razem ze strefą czasową, więc oczekiwane godziny uruchomień zgadzają się z serwerem.
Niektóre implementacje, na przykład cronie, przyjmują zmienną CRON_TZ na początku crontaba, aby uruchamiać harmonogramy w innej strefie. Obsługa różni się między wersjami crona, więc przetestuj to, zamiast zakładać, że działa.
Najdziwniejsze błędy powoduje zmiana czasu. Wiosną jedna godzina czasu lokalnego nie istnieje, jesienią jedna godzina występuje dwa razy. Zadanie zaplanowane mniej więcej między 01:00 a 03:00 czasu lokalnego może zostać pominięte, opóźnione albo uruchomione dwa razy, a każda implementacja crona radzi sobie ze zmianą inaczej. Najbezpieczniejsze rozwiązania:
Ustaw serwery na UTC, które nie ma zmiany czasu, a przeliczaj w głowie albo w komentarzu.
Jeśli serwer musi działać w czasie lokalnym, planuj ważne zadania poza godzinami, w których zmienia się czas.
Wpisz strefę czasową w komentarzu nad każdym zadaniem, żeby następna osoba nie musiała zgadywać.
Zadbaj o to, by zadania można było bezpiecznie uruchomić dwa razy, na przykład sprawdzając, czy dzisiejszy eksport już istnieje.
Pułapki środowiska i PATH
Polecenie, które działa w terminalu, a pod cronem zawodzi, prawie zawsze zawodzi przez środowisko. Cron nie wczytuje profilu powłoki, więc brakuje wielu rzeczy, które uważasz za oczywiste:
PATH jest minimalny, często to tylko /usr/bin:/bin. Polecenia z /usr/local/bin albo narzędzia zainstalowane przez nvm, pyenv, rbenv czy Composer nie zostaną znalezione. Używaj ścieżek bezwzględnych albo ustaw PATH na początku crontaba.
Powłoka to /bin/sh, a nie bash. Składnia dostępna tylko w bashu, taka jak [[ ]] czy source, nie zadziała. Ustaw SHELL=/bin/bash albo przenieś logikę do skryptu z shebangiem basha.
Znaki procentu są specjalne. W wierszu crontaba % zamienia się w znak nowej linii. date +%F trzeba zapisać jako date +\%F albo przenieść do skryptu.
Katalog roboczy to katalog domowy użytkownika, a nie Twój projekt. Ścieżki względne wskazują złe miejsce. Najpierw zrób cd do projektu.
Zmienne środowiskowe znikają. Klucze API i adresy baz danych eksportowane w .bashrc nie istnieją. Wczytuj je w skrypcie z pliku.
Wyjście przepada. Cron wysyła wyjście mailem na adres z MAILTO, jeśli poczta jest skonfigurowana, a na większości serwerów nie jest. Przekierowuj wyjście do pliku logu.
Uruchomienia mogą się nakładać. Jeśli zadanie uruchamiane co pięć minut czasem trwa siedem, działają naraz dwie kopie. flock -n pomija uruchomienie, dopóki poprzednie trzyma blokadę.
Niektóre wersje crona ignorują też ostatni wiersz, który nie kończy się znakiem nowej linii. Crontab, który uwzględnia to wszystko, wygląda tak:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=””
# Nightly database backup. Server time is UTC.
30 2 * * * cd /var/www/fjordstudio && flock -n /tmp/backup.lock ./bin/backup.sh >> /var/log/backup.log 2>&1
Jak przetestować wyrażenie cron
Przeczytaj je słowami. Wklej wyrażenie do parsera wyrażeń cron i sprawdź, czy opis i kilka najbliższych uruchomień zgadzają się z Twoim zamiarem.
Uruchom polecenie w środowisku crona. Okrojone środowisko odtwarza większość błędów typu „w terminalu działa”, zanim doczekasz się harmonogramu.
Raz zaplanuj je co minutę. Tymczasowo użyj * * * * *, zobacz, jak się uruchamia, a potem ustaw właściwy harmonogram.
Sprawdź, czy cron je uruchomił. W Debianie i Ubuntu: grep CRON /var/log/syslog albo journalctl -u cron; w systemach opartych na Red Hat: /var/log/cron albo journalctl -u crond.
Potwierdź, że crontab jest zainstalowany, poleceniem crontab -l jako użytkownik, który ma uruchamiać zadanie.
Aby zasymulować środowisko crona z poziomu powłoki:
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=”$HOME” /bin/sh -c '/usr/local/bin/backup.sh’
Połącz każde zadanie z heartbeatem
Poprawne wyrażenie mówi cronowi tylko, kiedy uruchomić zadanie. Nie mówi nic o tym, czy zadanie się wykonało albo zakończyło. Restart, nadpisany crontab czy brakująca zmienna kończą się błędem po cichu.
Tę lukę zamyka heartbeat. Po udanym zakończeniu zadanie wywołuje unikalny adres URL, a usługa monitorowania powiadamia Cię, gdy żądanie nie dotrze zgodnie z harmonogramem:
30 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 –retry 3 https://heartbeat.example.com/your-unique-id > /dev/null
Ustaw oczekiwany interwał heartbeatu zgodnie z harmonogramem wyrażenia cron, z zapasem na wolniejsze uruchomienia: 24 godziny z niewielkim naddatkiem dla zadania nocnego, godzina z niewielkim naddatkiem dla cogodzinnego. Tak działa monitorowanie zadań cron w Uptimecheck: alert, gdy brakuje pingu, i powiadomienie o przywróceniu, gdy zadanie znów się zgłosi. Monitory crona przyjmują harmonogram z crontaba w Twojej strefie czasowej, obsługują pingi startu i zakończenia w jednym monitorze, wysyłają alert, gdy uruchomienie przekroczy maksymalny czas trwania, i mogą zapisywać kod wyjścia oraz wyjście zadania. Konfigurację szczegółowo opisuje poradnik o tym, jak monitorować zadania cron za pomocą heartbeatów, a dobre nawyki wokół niej – tekst o zapobieganiu pominiętym zadaniom cron.
Gdy brakuje heartbeatu, przyczyna często leży po stronie serwera: zapełniony dysk, wyczerpana pamięć albo nieplanowany restart. Monitorowanie serwerów obok heartbeatów pokazuje tę przyczynę w tej samej chwili.
Lista typowych błędów
Ograniczenie godziny przy minucie pozostawionej jako *.
Ustawienie obu pól dni w oczekiwaniu na I zamiast LUB.
Założenie, że serwer działa w Twojej strefie czasowej.
Planowanie ważnych zadań na godzinę zmiany czasu.
Ścieżki względne, składnia basha albo % bez ukośnika.
Zadanie bez heartbeatu, więc nikt nie zauważy, gdy przestanie działać.
Podsumowanie
Pięć pól, cztery znaki specjalne i kilka skrótów wystarczą do niemal każdego harmonogramu, jakiego będziesz potrzebować. Problemem rzadko jest samo wyrażenie. Są nim strefy czasowe, reguła LUB dla pól dni i ubogie środowisko crona. Zapisuj strefę czasową przy każdym zadaniu, używaj ścieżek bezwzględnych, loguj wyjście i daj każdemu ważnemu zadaniu heartbeat.
Heartbeaty Uptimecheck są dostępne w każdym planie, także w planie Free, obok monitorowania dostępności, SSL, domen i DNS. Rozpocznij 14-dniowy bezpłatny okres próbny, bez karty kredytowej.