Crontab cheat sheet: 50+ wyrażeń cron, składnia i przewodnik po nowoczesnych schedulerach
Wyrażenie cron to pięć pól (minuta, godzina, dzień miesiąca, miesiąc, dzień tygodnia), a po nich polecenie. Ta gramatyka rządzi harmonogramami w Unixie od 1979 roku i dziś napędza Kubernetes CronJob, GitHub Actions, AWS EventBridge oraz wyzwalacze cron w Vercel. Wystarczy nauczyć się raz, żeby planować zadania wszędzie.
Ta strona jest dla deweloperów, którzy potrzebują wyrażenia tu i teraz: zadanie w Linuksie, Kubernetes CronJob, wyzwalacz GitHub Actions albo diagnoza, dlaczego pięciominutowy job odpala co godzinę. Po gotowe wyrażenia można przejść do tabeli szybkiej referencji, po reguły pól do sekcji o składni, a sprawdzić wyrażenie na żywo pomoże Generator crontab — privacy-first alternatywa dla crontab guru działająca w przeglądarce.
Tabela szybkiej referencji wyrażeń cron
Trzydzieści wyrażeń pokrywa około 90% realnych potrzeb. Każde jest poprawnym pięciopolowym crone w wersji POSIX, można je wkleić do crontab -e, do schedule: w Kubernetes albo do cron: w GitHub Actions.
| Harmonogram | Wyrażenie cron | Po polsku |
|---|---|---|
| Co minutę | * * * * * | każda minuta przez całą dobę, każdego dnia |
| Co 5 minut | */5 * * * * | minuta 0, 5, 10, …, 55 |
| Co 15 minut | */15 * * * * | minuta 0, 15, 30, 45 |
| Co 30 minut | */30 * * * * | minuta 0 i 30 |
| Co godzinę | 0 * * * * | o pełnej godzinie |
| Co 2 godziny | 0 */2 * * * | godzina 0, 2, 4, …, 22 |
| Co 6 godzin | 0 */6 * * * | godzina 0, 6, 12, 18 |
| Dwa razy dziennie (9:00 i 21:00) | 0 9,21 * * * | minuta 0 godzin 9 i 21 |
| Każdy dzień roboczy o 9:00 | 0 9 * * 1-5 | pn-pt 09:00 |
| Każdy weekend o 9:00 | 0 9 * * 0,6 | sob i nd 09:00 |
| Codziennie o północy | 0 0 * * * | każdego dnia 00:00 |
| Codziennie o 2:30 | 30 2 * * * | okno wsadowe o niskim ruchu |
| W każdy poniedziałek o 9:00 | 0 9 * * 1 | poniedziałki 09:00 |
| W każdy piątek o 17:00 | 0 17 * * 5 | piątki 17:00 |
| W każdą niedzielę o północy | 0 0 * * 0 | odpowiednik @weekly |
| Pierwszy dnia miesiąca o północy | 0 0 1 * * | 1. dzień 00:00, odpowiednik @monthly |
| 15. dnia każdego miesiąca w południe | 0 12 15 * * | środek miesiąca, okno wypłat |
| Kontrola ostatniego dnia (wrapper) | 0 0 28-31 * * + skrypt | wymaga sprawdzenia daty |
| Kwartalnie (1. dzień sty/kwi/lip/paź) | 0 0 1 JAN,APR,JUL,OCT * | pierwszy dzień każdego kwartału |
| Rocznie (1 stycznia) | 0 0 1 1 * lub @yearly | północ Nowego Roku |
| Co 5 min w dni robocze 9-17 | */5 9-17 * * 1-5 | odpytywanie w godzinach pracy |
| Co 30 min w weekendy | */30 * * * 0,6 | monitorowanie sob/nd |
| Dwa razy na godzinę, 15 i 45 | 15,45 * * * * | przesunięcie od szczytu o :00 |
| Pierwszy poniedziałek (wrapper) | 0 9 1-7 * 1 + sprawdzenie AND | potrzebny wrapper (zobacz niżej) |
| Makra | @hourly @daily @weekly @monthly @yearly | niestandardowe, ale szeroko wspierane |
| Tylko przy restarcie | @reboot | niestandardowe, tylko vixie cron |
Każde z tych wyrażeń można wkleić do Generator crontab, żeby zobaczyć podgląd kolejnych pięciu odpaleń. To najszybszy test przed wdrożeniem.
Składnia cron na czynniki pierwsze, 5 pól
Wyrażenie cron to pięć pól oddzielonych białymi znakami plus polecenie. Każde pole steruje jednym wymiarem harmonogramu. Tę samą składnię stosują wszystkie schedulery omawiane w tym przewodniku.
┌──────────── minuta (0 - 59)
│ ┌────────── godzina (0 - 23)
│ │ ┌──────── dzień miesiąca (1 - 31)
│ │ │ ┌────── miesiąc (1 - 12 lub JAN-DEC)
│ │ │ │ ┌──── dzień tygodnia (0 - 6 lub SUN-SAT; 0 i 7 oznaczają niedzielę)
│ │ │ │ │
* * * * * polecenie-do-uruchomienia
Mnemonik: „Moja Godzina Dnia Miesiąca Tygodnia” (Minuta, Godzina, Dzień miesiąca, Miesiąc, Dzień tygodnia). Od lewej do prawej, od najmniejszej jednostki do największej.
Dozwolone wartości pole po polu
| Pole | Zakres | Aliasy | Uwagi |
|---|---|---|---|
| Minuta | 0-59 | brak | 0 oznacza „o pełnej godzinie” |
| Godzina | 0-23 | brak | zegar 24-godzinny; 0 to północ, 12 to południe |
| Dzień miesiąca | 1-31 | brak | nieistniejące dni miesiąca po cichu nigdy nie odpalą (31 lutego) |
| Miesiąc | 1-12 | JAN, FEB, MAR, …, DEC | wielkość liter bez znaczenia |
| Dzień tygodnia | 0-7 | SUN, MON, TUE, …, SAT | zarówno 0, jak i 7 oznaczają niedzielę |
Operatory szczegółowo
Pięć operatorów obsługuje każde standardowe wyrażenie cron:
| Operator | Znaczenie | Przykład | Rozwija się do |
|---|---|---|---|
* | dowolna wartość | * * * * * | co minutę |
, | lista | 0 9,12,17 * * * | 09:00, 12:00, 17:00 |
- | zakres włączny | 0 9-17 * * * | co godzinę od 09:00 do 17:00 |
/ | krok | */15 * * * * | minuta 0, 15, 30, 45 |
| mieszane | łączone | 0 9-12,14-17 * * * | rano + popołudnie, z pominięciem lunchu |
Operator kroku zasługuje na uwagę. */N jest zakotwiczony do najniższej wartości pola, a nie do bieżącej chwili. */15 znaczy „minuta 0, 15, 30, 45 każdej godziny”, a nie „co 15 minut od teraz”. Zapis o 12:03 daje kolejne odpalenie o 12:15. Z bazą inną niż wildcard 5/15 czyta się jako „startuj o 5, potem co 15”: minuta 5, 20, 35, 50.
Nazwane miesiące i dni tygodnia
Miesiące i dni tygodnia można zapisać nazwami, bez rozróżniania wielkości liter:
0 0 1 JAN,APR,JUL,OCT * # pierwszy dzień każdego kwartału
0 9 * * MON-FRI # dni robocze o 9:00
0 17 * * FRI # piątek o 17:00
Nazwy czytają się czytelniej w code review; forma liczbowa jest nieco lepiej przenośna. W jednym projekcie warto trzymać się jednej konwencji.
Niestandardowe makra: @reboot, @daily i spółka
Większość implementacji cron akceptuje sześć makr skrótowych:
| Makro | Rozwija się do | Znaczenie |
|---|---|---|
@yearly / @annually | 0 0 1 1 * | raz w roku, 1 stycznia o północy |
@monthly | 0 0 1 * * | pierwszy dnia każdego miesiąca o północy |
@weekly | 0 0 * * 0 | w każdą niedzielę o północy |
@daily / @midnight | 0 0 * * * | codziennie o północy |
@hourly | 0 * * * * | o pełnej godzinie |
@reboot | (specjalne) | raz, przy starcie demona cron |
Te makra są niestandardowe: vixie cron i cronie je obsługują, ale Kubernetes CronJob, GitHub Actions i AWS EventBridge już nie. Dla przenośnych wyrażeń lepiej pisać formę pięciopolową. @reboot rzadko działa w kontenerach, gdzie demon cron nie zawsze jest procesem init.
50+ gotowych wyrażeń cron (pogrupowane według zastosowań)
Tabela szybkiej referencji pokrywa typowe przypadki. Ta sekcja schodzi głębiej w sześć grup z gęstszymi przykładami cron jobów.
Co N minut
* * * * * # co minutę
*/2 * * * * # co 2 minuty
*/5 * * * * # co 5 minut — klasyczny przypadek „cron expression every 5 minutes”
*/10 * * * * # co 10 minut
*/15 * * * * # co 15 minut
*/30 * * * * # co 30 minut
0,30 * * * * # jawnie minuty 0 i 30 (to samo, co */30)
*/45 * * * * # UWAGA: odpala się tylko o 0 i 45, potem zawija
*/45 to typowy strzał w stopę: minuta ma zakres 0-59, więc wyrażenie ląduje na 0 i 45, a w następnej godzinie zawija się od zera. Dla prawdziwego cyklu co 45 minut potrzeba zewnętrznego workera.
Warianty godzinne
0 * * * * # co godzinę o :00
30 * * * * # co godzinę o :30
0 */2 * * * # co 2 godziny, parzysta godzina
0 */6 * * * # co 6 godzin
0 */12 * * * # dwa razy dziennie, o 00:00 i 12:00
15 */2 * * * # co 2 godziny, z przesunięciem 15 min (omija szczyt o :00)
Codziennie o konkretnej porze
0 0 * * * # północ (= @daily / @midnight)
30 2 * * * # 02:30 — okno wsadowe o niskim ruchu
0 9 * * * # 09:00
45 23 * * * # 23:45 — podsumowania na koniec dnia
0 9,12,17 * * * # trzy razy dziennie
0 9-17 * * * # co godzinę od 09:00 do 17:00 włącznie
Harmonogramy tygodniowe
0 9 * * 1-5 # dni robocze o 9:00
0 9 * * 0,6 # weekendy o 9:00
0 18 * * 5 # piątki o 18:00
0 0 * * 0 # niedziela o północy (= @weekly)
0 9 * * MON,WED,FRI # pon/śr/pt o 9:00
*/30 9-17 * * 1-5 # co 30 min, godziny pracy, dni robocze
Miesięczne i kwartalne
0 0 1 * * # 1. dnia miesiąca o północy (= @monthly)
0 0 15 * * # 15. — okno wypłat
0 0 1,15 * * # 1. i 15. — co pół miesiąca
0 0 1 */3 * # kwartalnie: pierwszy stycznia, kwietnia, lipca, października
0 0 1 JAN,APR,JUL,OCT * # to samo, miesiące po nazwie
0 0 28-31 * * # ostatnie kilka dni — sparować z wrapperem sprawdzającym datę
POSIX nie ma natywnego wyrażenia dla „ostatniego dnia miesiąca”. Trzeba uruchomić wrapper, który sprawdza date -d tomorrow +%d = 01, albo użyć schedulera z natywnym wsparciem (Quartz ma L, Kubernetes nie).
Rocznie i makra skrótowe
0 0 1 1 * # 1 stycznia o północy (= @yearly / @annually)
0 0 25 12 * # Boże Narodzenie o północy
@yearly # = 0 0 1 1 *
@monthly # = 0 0 1 * *
@weekly # = 0 0 * * 0
@daily # = 0 0 * * *
@hourly # = 0 * * * *
@reboot # specjalne: raz przy starcie demona (tylko vixie cron)
Wszystkie te wyrażenia można wkleić do Generator crontab, żeby zobaczyć podgląd kolejnych pięciu odpaleń. To najtańszy smoke test przed wdrożeniem.
Cron kontra timery systemd kontra schedulery chmurowe
Cron jest domyślnym wyborem, ale nie zawsze najlepszym. Poniżej porównanie siedmiu najczęściej spotykanych schedulerów. Przydaje się przy wyborze między cron a timerami systemd, między Kubernetes CronJob a wyzwalaczem cron w Vercel albo przy migracji z crontab do zarządzanej chmury.
| Funkcja | vixie cron | timer systemd | K8s CronJob | GHA schedule | AWS EventBridge | Vercel Cron | Cloudflare Workers |
|---|---|---|---|---|---|---|---|
| Składnia pól | 5-polowa POSIX | spec OnCalendar | 5-polowa POSIX + timeZone | 5-polowa POSIX | 6-polowy Quartz z ? | 5-polowa POSIX | 5-polowa POSIX |
| Minimalny interwał | 1 minuta | 1 sekunda | 1 minuta | best-effort, zalecane ≥15 min | 1 minuta | 1 minuta (plan Pro) | 1 minuta |
| Jawna strefa czasowa | CRON_TZ= | Persistent=true | spec.timeZone (1.27+) | tylko UTC | ScheduleExpressionTimezone | tylko UTC | tylko UTC |
| Nadrabianie pominiętych | nie (użyj anacron) | tak (Persistent=true) | tak (startingDeadlineSeconds) | nie | tak | nie | nie |
| Retry / backoff | nie | częściowe | tak (backoffLimit) | retry po porażce | tak | nie | tak |
| Kontrola współbieżności | nie (użyj flock) | częściowa | tak (concurrencyPolicy) | nie | nie | nie | nie |
Wsparcie @reboot | tak | tak (przez OnBootSec=) | nie | nie | nie | nie | nie |
Timery systemd, kiedy warto je wybrać zamiast cron
Na Linuksie opartym o systemd timery to poważna alternatywa: czytelna składnia kalendarzowa, integracja z journalem i nadrabianie pominiętych wywołań. Timer i odpowiadająca mu usługa wyglądają tak:
# daily-report.timer
[Unit]
Description=Run daily report at 9 AM
[Timer]
OnCalendar=*-*-* 09:00:00
Persistent=true
Unit=daily-report.service
[Install]
WantedBy=timers.target
# daily-report.service
[Unit]
Description=Daily report job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/daily-report.sh
User=reporter
Włącza się to przez systemctl enable --now daily-report.timer. Mocnym argumentem jest Persistent=true: jeśli maszyna była wyłączona o 9:00, timer odpali się zaraz po starcie. Vixie cron nie ma odpowiednika bez anacron. O hardeningu usług piszemy w security best practices.
Kubernetes CronJob
Kubernetes opakowuje harmonogram POSIX prymitywami do kontroli współbieżności, historii i jawnej strefy czasowej:
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
timeZone: "America/New_York" # Kubernetes 1.27+
concurrencyPolicy: Forbid # nigdy nie odpalaj dwóch naraz
startingDeadlineSeconds: 300 # pomiń, jeśli opóźnione >5 min
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: reporter
image: reporter:1.4.0
command: ["/usr/local/bin/report.sh"]
concurrencyPolicy: Forbid to odpowiednik flock. Bez tego długo działający job nakłada się na następcę. Wszystkie pokrętła opisuje sekcja referencyjna pól.
Pułapki schedulera GitHub Actions
GitHub Actions akceptuje standardowy pięciopolowy cron POSIX:
on:
schedule:
- cron: '0 9 * * 1-5' # dni robocze o 9:00 UTC
Best-effort: przy wysokim obciążeniu runnerów GitHuba zadania potrafią odpalić się z kilkuminutowym opóźnieniem lub w ogóle wypaść. Interwałów krótszych niż piętnaście minut lepiej unikać. Strefy czasowej ustawić się nie da, zawsze UTC.
AWS EventBridge, sześciopolowy w stylu Quartz
AWS EventBridge używa cron o smaku Quartz z sześcioma polami i wymaganym ? w jednym ze slotów daty:
cron(0 9 * * ? *)
Kolejność pól: Minuty Godziny Dzień-miesiąca Miesiąc Dzień-tygodnia Rok. Jedno z pól dnia musi mieć wartość ?, jeśli drugie jest ograniczone (sposób, w jaki Quartz rozwiązuje POSIX-ową dwuznaczność OR). Bezpośrednie skopiowanie z linuksowego crontab nie przejdzie walidacji.
Vercel Cron, Cloudflare Workers, Render Cron Jobs
Nowsze platformy serverless standaryzują się wokół pięciopolowego POSIX. Vercel cron job zapisuje się w vercel.json jako { "crons": [{ "path": "/api/cron/nightly", "schedule": "0 2 * * *" }] }. Cron Triggers w Cloudflare Workers idą w wrangler.toml:
[triggers]
crons = ["*/15 * * * *", "0 9 * * 1-5"]
Render korzysta z render.yaml. Wszystkie trzy działają w UTC bez możliwości nadpisania strefy per harmonogram. Trzeba projektować od początku w UTC.
7 pułapek przy debugowaniu cron (i jak je łapać)
Większość zgłoszeń „mój cron job się nie uruchamia” ma jedną z siedmiu przyczyn źródłowych. Przed obwinianiem schedulera warto przejść tę listę.
Pułapka 1: minimalna PATH
Cron startuje zadania z minimalną $PATH, zwykle /usr/bin:/bin. Interaktywny shell ma /usr/local/bin, ~/.cargo/bin i tuzin wpisów z .bashrc. Pod cronem nie ma żadnego. To najczęstszy problem z PATH w środowisku cron.
Objaw: node: command not found. Naprawa: ustawić PATH na początku crontab albo używać ścieżek absolutnych.
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
*/15 * * * * /usr/local/bin/poll-api.sh
0 9 * * * /home/deploy/.cargo/bin/my-rust-cli
Pułapka 2: stdout i stderr cicho znikają
Domyślnie wyjście crona idzie do mail spoola, którego nikt nie czyta. Zadanie kończy się porażką po cichu. Należy przekierować oba strumienie:
*/15 * * * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1
Dla wyjścia JSON podaj wynik przez jq; do wyłuskiwania linii logów zajrzyj do naszego regex cheat sheet. Dla timerów systemd wyjście zbiera journalctl -u your-timer.service.
Pułapka 3: dryf stref czasowych między dev i prod
Wyrażenie 0 9 * * * napisane na laptopie w Nowym Jorku miało odpalać o 9:00 czasu wschodniego. Serwer chodzi w UTC. Cron odpala się o 9:00 UTC, czyli o 4:00 czasu wschodniego, zanim ktokolwiek to zauważy. Naprawa: ustawić serwery na UTC i pisać harmonogramy w UTC albo jawnie przypiąć strefę.
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/morning-report.sh
CRON_TZ działa w vixie cron 3.0+; Kubernetes 1.27+ ma spec.timeZone; AWS EventBridge ma ScheduleExpressionTimezone; GitHub Actions zawsze UTC. O UTC, DST i matematyce epoch piszemy w Unix timestamp guide.
Pułapka 4: nieuciekane % w poleceniach
Cron traktuje nieuciekane % jako znaki nowej linii i resztę wiersza wysyła do polecenia jako stdin. Przez to date +"%Y-%m-%d" się łamie. Każde % trzeba uciec jako \% albo przenieść logikę do skryptu:
0 0 * * * echo "Run at $(date +"\%Y-\%m-\%d")" >> /tmp/log
Pułapka 5: nakładające się uruchomienia
Job */5 * * * *, który czasem zajmuje siedem minut, wystartuje kolejną instancję, zanim poprzednia skończy. Dwie kopie biją się o ten sam wiersz, plik blokady i limit API. Serializuje to flock:
*/5 * * * * flock -n /tmp/job.lock /usr/local/bin/job.sh
-n powoduje natychmiastowe wyjście, jeśli blokada jest trzymana. W Kubernetesie odpowiednikiem jest concurrencyPolicy: Forbid. Uprawnienia plików blokady mają znaczenie, zobacz security best practices.
Pułapka 6: @reboot w kontenerach
@reboot uruchamia się raz, w momencie startu demona cron. W maszynie wirtualnej to moment bootowania. W kontenerze demon cron zwykle nie jest PID 1 i wcale może się nie uruchomić. @reboot w kontenerach lepiej odpuścić, a logikę „raz przy starcie” upchnąć w entrypoincie albo init container.
Pułapka 7: POSIX-owa semantyka OR dla dnia miesiąca i dnia tygodnia
Najbardziej kosztowna pułapka cron. Reguła POSIX: gdy oba pola, dzień miesiąca i dzień tygodnia, są ograniczone (żadne nie ma *), harmonogram odpala się, gdy pasuje dowolne z nich.
0 0 1 * 5 wygląda jak „o północy 1. dnia miesiąca, ale tylko w piątki”, a w rzeczywistości odpala się 1. dnia miesiąca ORAZ w każdy piątek: od sześciu do dziesięciu dodatkowych odpaleń miesięcznie.
# ŹLE: wygląda jak „1. dnia miesiąca, ale tylko jeśli piątek”
0 0 1 * 5
# DOBRZE: wybierz jedno ograniczenie
0 0 1 * * # każdy 1. dzień miesiąca
0 0 * * 5 # każdy piątek
# Semantyka AND wymaga wrappera
0 0 1-7 * 5 [ "$(date +\%u)" = "5" ] && /script # tylko pierwszy piątek
Podejrzane wyrażenia warto wkleić do Generator crontab. Podgląd kolejnego uruchomienia od razu pokazuje pułapkę OR.
Nowoczesne schedulery, kiedy NIE używać cron
Cron pasuje do „uruchom to polecenie mniej więcej o tej porze, w stałym cyklu”. Do kilku sąsiednich problemów już nie:
- Workflow z zależnościami (uruchom A, potem B, jeśli A się udało): Airflow, Prefect, Dagster.
- Retry, wykładniczy backoff, kolejki dead-letter: Temporal, AWS Step Functions, Sidekiq.
- Interwały krótsze niż minuta: długo żyjący worker, który śpi między iteracjami.
- Precyzja co do sekundy: dedykowany demon; zarządzane schedulery dystansują się od dokładnego timingu.
- Praca sterowana zdarzeniami: webhooki, kolejki wiadomości, strumienie change-data-capture.
Cron nie znika. Airflow, Step Functions i Sidekiq nadal akceptują wyrażenia cron jako wejście swoich workflow. Pięciopolowa gramatyka jest wielokrotnego użytku.
Kubernetes CronJob, referencja pól {#kubernetes-cronjob-referencja-pol}
Macierz porównawcza powyżej pokazała minimalny CronJob. Pełna referencja pól dla składni Kubernetes CronJob:
| Pole | Domyślnie | Do czego służy |
|---|---|---|
schedule | wymagane | pięciopolowe wyrażenie cron POSIX |
timeZone | TZ kontrolera | jawna strefa czasowa (1.27+); użyj nazw IANA |
concurrencyPolicy | Allow | Forbid pomija nowe odpalenia, gdy poprzednie wciąż trwa; Replace anuluje poprzednie |
startingDeadlineSeconds | bez ograniczeń | pomiń, jeśli opóźnienie przekracza tę wartość |
successfulJobsHistoryLimit | 3 | liczba zachowanych udanych Jobów |
failedJobsHistoryLimit | 1 | liczba zachowanych nieudanych Jobów |
suspend | false | pauza bez usuwania |
backoffLimit | 6 | liczba ponowień Poda, zanim Job zostanie oznaczony jako Failed |
activeDeadlineSeconds | nie ustawione | twardy limit czasu działania Poda |
ttlSecondsAfterFinished | nie ustawione | auto-usunięcie Jobu po podanej liczbie sekund |
Dwie częste pułapki: pominięcie timeZone sprawia, że harmonogram idzie za strefą czasową hosta kube-controller-managera (na zarządzanym Kubernetesie nieprzewidywalna); przy harmonogramie minutowym domyślny successfulJobsHistoryLimit: 3 gromadzi trzy obiekty Job na minutę, dopóki nie ustawisz ttlSecondsAfterFinished.
Odpowiedniki cron na różnych platformach
macOS launchd. Apple zaleca launchd zamiast crona. Zadanie launchd to plik .plist w ~/Library/LaunchAgents/:
<plist version="1.0"><dict>
<key>Label</key><string>com.example.daily</string>
<key>ProgramArguments</key><array><string>/usr/local/bin/daily.sh</string></array>
<key>StartCalendarInterval</key>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer></dict>
</dict></plist>
Ładuje się go przez launchctl load ~/Library/LaunchAgents/com.example.daily.plist. W odróżnieniu od cron, launchd nadrabia odpalenia pominięte podczas sleep/wake.
Windows Task Scheduler korzysta z schtasks:
schtasks /create /tn "DailyReport" /tr "C:\scripts\report.bat" /sc DAILY /st 09:00
schtasks /create /tn "EveryFifteen" /tr "C:\scripts\poll.bat" /sc MINUTE /mo 15
W WSL natywny linuksowy cron działa, ale zatrzymuje się wraz z końcem sesji. Żeby trzymać zadania WSL w trybie always-on, użyj Task Schedulera.
Cron w kontenerach Dockera. Większość slim images (alpine, debian-slim, distroless) nie ma demona cron. Trzeba zainstalować cronie albo busybox-cron i uruchomić go jako PID 1 razem z tini lub s6-overlay. Prawie zawsze lepiej jednak sięgnąć po Kubernetes CronJob.
Wskazówki i wzorce zaawansowane
Ostatni dzień miesiąca
Cron nie ma natywnego operatora „ostatniego dnia”. Trzeba uruchamiać się codziennie w oknie 28-31 i sprawdzić, czy jutro jest 1.:
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/eom.sh
N-ty dzień tygodnia w miesiącu
„Pierwszy poniedziałek” używa tego samego wzorca z wrapperem: ograniczyć do dni 1-7, potem sprawdzić dzień tygodnia:
0 9 1-7 * * [ "$(date +\%u)" = "1" ] && /usr/local/bin/first-monday.sh
Dla „ostatniego piątku” weź dni 25-31 plus sprawdzenie dnia tygodnia.
Losowe przesunięcie, żeby rozproszyć obciążenie
Gdy wiele maszyn uruchamia ten sam cron, 0 0 * * * rodzi efekt stampede o północy UTC. Dodaj losowe opóźnienie:
RANDOM_DELAY=10 # cronie / anacron, w minutach
0 0 * * * /usr/local/bin/job.sh
0 0 * * * sleep $((RANDOM \% 600)); /usr/local/bin/job.sh # przenośnie
Monitorowanie heartbeatu
Cron pada po cichu. Wzorzec dead-man’s switch: zadanie po każdym udanym uruchomieniu pinguje usługę monitorującą, a usługa alarmuje, gdy oczekiwany ping nie dotrze. Healthchecks.io, Cronitor i Dead Man’s Snitch oferują darmowe plany.
*/15 * * * * /usr/local/bin/job.sh && curl -fsS --retry 3 https://hc-ping.com/your-uuid
Logikę monitorowania rozgałęzioną po kodach odpowiedzi (200 zdrowy, 429 rate-limited, 503 degradacja) opisuje nasz HTTP status codes cheat sheet.
Idempotencja to cecha zadania, nie schedulera
Cron nie ma retry, nie nadrabia pominiętych odpaleń i nie kontroluje współbieżności. Najpewniejszy fix to zrobienie samego zadania bezpiecznym do wielokrotnego uruchomienia. Zamiast „wyślij dzisiejszy raport o 9:00” zaprojektuj je jako „wyślij dzisiejszy raport, jeśli jeszcze nie został wysłany”. Pominięte odpalenia, duplikaty i ręczne nadrabianie zbiegają się wtedy do tego samego stanu.
FAQ
Czy */5 * * * * naprawdę odpala się co 5 minut?
Prawie — */5 * * * * jest zakotwiczone do minuty 0, a nie do „co 5 minut od teraz”. Odpala się o minucie 0, 5, 10, …, 55 każdej godziny. Krok */N jest względny wobec najniższej wartości pola, nie wobec bieżącej chwili. Zapis o 12:03 daje kolejne odpalenie o 12:05, nie o 12:08.
Co oznacza 0 0 * * * w cron?
0 0 * * * oznacza codziennie o północy (00:00) w lokalnej strefie czasowej serwera. Pola: minuta 0, godzina 0, dowolny dzień miesiąca, dowolny miesiąc, dowolny dzień tygodnia. Odpowiednik makr @daily lub @midnight. Żeby przypiąć strefę, dodaj CRON_TZ=America/New_York na początku crontab.
Jak uruchomić cron job co 30 sekund?
Standardowym POSIX-owym cronem się nie da, minimalna granularność to jedna minuta. Trzy obejścia: dwa zadania przesunięte o * * * * * z sleep 30 && na jednym; timer systemd z OnCalendar=*:*:0/30; albo długo żyjący worker śpiący między iteracjami. To ostatnie jest zwykle właściwe.
Jakiej strefy czasowej używa cron domyślnie?
Lokalnej strefy systemowej serwera (/etc/timezone lub zmienna środowiskowa TZ). Cron o 9:00 na serwerze w UTC odpali się o 4:00 czasu wschodniego. Naprawa: ustaw CRON_TZ= na początku crontab albo trzymaj serwery w UTC i projektuj harmonogramy w UTC. GitHub Actions zawsze chodzi w UTC; Kubernetes 1.27+ wspiera spec.timeZone.
Dlaczego mój cron job się nie uruchamia?
Jeśli twoje zadanie cron się nie uruchamia, sprawdź po kolei: czy demon cron działa (systemctl status cron); czy $PATH jest ustawiona w crontab; czy stderr jest przechwytywany (>> log 2>&1); czy crontab użytkownika jest załadowany (crontab -l); czy % w poleceniach jest uciekane; czy strefa czasowa jest taka, jak się spodziewasz. Większość zgłoszeń „nie działa” rozwiązuje punkt drugi lub trzeci.
Czy składnia Kubernetes CronJob jest taka sama jak linuksowego cron?
Tak, jeśli chodzi o pole schedule: oba używają pięciopolowego cron POSIX. Kubernetes dokłada spec.timeZone (1.27+), concurrencyPolicy do kontroli nakładania się, startingDeadlineSeconds do nadrabiania pominiętych odpaleń i suspend: true do pauzy. Linuksowy cron nie ma żadnego z tych mechanizmów, w jego miejscu sięga się po flock i anacron.
Czym różnią się @reboot i @daily?
@daily to makro dla 0 0 * * *, czyli codziennie o północy, w stałym harmonogramie. @reboot odpala się raz, przy starcie demona cron, bez cyklu. @reboot obsługują vixie cron i cronie, ale nie Kubernetes CronJob, GitHub Actions ani AWS EventBridge. W kontenerach @reboot rzadko się odpala.
Jaka jest różnica między cron a crontab?
cron to demon działający w tle, który uruchamia zaplanowane zadania; crontab to plik, który je wymienia (oraz polecenie crontab do edycji tego pliku). Demon okresowo czyta crontab każdego użytkownika i uruchamia polecenia, których czas wykonania pasuje do wyrażenia cron. Krótko mówiąc: cron to silnik, crontab to przepis.