Konfiguracja cronjoba: harmonogram, uprawnienia i najczęstsze błędy
Zadanie działa w shellu, ale nie w cronie. Ten przewodnik wyjaśnia pięć pól czasowych, różnicę między crontabem użytkownika a /etc/cron.d, pułapkę PATH oraz sposób, w jaki udowodnisz, że zadanie naprawdę się wykonało.
Cronjob konfiguruje się w pięć minut, a potem potrafi kosztować całe godziny. Polecenie działa w shellu bez zarzutu, z crona nie dzieje się nic, a w logu w najlepszym razie widnieje informacja, że cron coś uruchomił. Ten artykuł zajmuje się dokładnie tymi miejscami, w których zwykłe poradniki się kończą: prawdziwymi komunikatami błędów, różnicami między dystrybucjami oraz pytaniem, po czym poznasz, że zadanie naprawdę przebiegło do końca, a nie tylko wystartowało.
Wszystkie informacje dotyczą Debiana 13 (trixie), Debiana 12 (bookworm), Ubuntu 24.04 LTS oraz Ubuntu 22.04 LTS. Te cztery systemy korzystają z tego samego crona (wariant Vixie Cron przygotowany przez Debiana), a nie z cronie znanego z Red Hata i Fedory. Ta różnica okaże się później kluczowa przy strefie czasowej.
Czy usługa cron w ogóle działa?
W pełnych instalacjach cron jest obecny. W minimalnych obrazach chmurowych, w bazowych obrazach kontenerów i w odchudzonych instalacjach netinst pakietu regularnie brakuje. To pierwsze pytanie, które warto wyjaśnić, zanim zaczniesz cokolwiek debugować.
command -v crontab
systemctl status cron
Celowo command -v crontab, a nie command -v cron: sama usługa leży w /usr/sbin, a ten katalog znajduje się na Debianie wyłącznie w ścieżce wyszukiwania roota. Jako zwykły użytkownik nie zobaczysz tam po prostu żadnego wyniku, mimo że cron jest zainstalowany. Na Ubuntu 24.04 zapytanie działa również z konta bez uprawnień. crontab leży natomiast wszędzie w /usr/bin i jest tym samym widoczny dla każdego, a systemctl status cron i tak odpowiada na ważniejsze pytanie, czyli czy usługa faktycznie działa.
Jeśli usługi brakuje, doinstaluj ją:
apt-get update
apt-get install -y cron
systemctl enable --now cron
Usługa nazywa się na Debianie i Ubuntu cron, a nie crond. Kto przyzwyczaił się do Red Hata, dostanie tutaj Unit crond.service could not be found. i będzie szukał w niewłaściwym miejscu.
W normalnej pracy nie ma obowiązku restartu: debianowy cron obserwuje katalogi z crontabami przez inotify i sam wczytuje zmiany. Po edycji crontaba nie musisz więc niczego restartować. Wyjątkiem są zmiana systemowej strefy czasowej oraz modyfikacje pliku /etc/default/cron.
Pięć pól i pułapka w piątym
Każdy wiersz zaczyna się od pięciu pól czasowych, a po nich następuje polecenie.
| Pole | Zakres | Uwaga |
| Minuta | 0 do 59 | |
| Godzina | 0 do 23 | format 24-godzinny, bez strefy czasowej |
| Dzień miesiąca | 1 do 31 | |
| Miesiąc | 1 do 12 | także jan do dec |
| Dzień tygodnia | 0 do 7 | 0 i 7 oznaczają niedzielę, także sun do sat |
30 4 * * * /usr/local/bin/backup.sh # codziennie o 04:30
*/10 * * * * /usr/local/bin/check.sh # co 10 minut
0 2 * * 0 /usr/local/bin/weekly.sh # w niedziele o 02:00
15 3 1 * * /usr/local/bin/monthly.sh # 1. dnia miesiąca o 03:15
0 9-17 * * 1-5 /usr/local/bin/business.sh # w dni robocze co godzinę od 9 do 17
Dwa szczegóły są niemal zawsze rozumiane opacznie.
Dzień miesiąca i dzień tygodnia łączy LUB, a nie ORAZ. Gdy tylko oba pola są zawężone, czyli żadne nie zawiera gwiazdki, zadanie ruszy, jeśli pasuje jedno z nich. 0 3 13 * 5 nie oznacza „piątku trzynastego”, tylko „każdego trzynastego dnia miesiąca i dodatkowo każdego piątku”. Jeśli naprawdę chodzi ci o piątek trzynastego, sprawdź to w samym skrypcie.
Krok nie dzieli równomiernie. */7 * * * * uruchamia się w minutach 0, 7, 14, 21, 28, 35, 42, 49 i 56, po czym licznik przeskakuje na kolejną godzinę. Między 56 a 0 mijają więc tylko cztery minuty. Dotyczy to każdego interwału, który nie dzieli równo 60 względnie 24. Dla „co 90 minut” nie ma w cronie czystego zapisu, tutaj lepszym wyborem jest timer systemd.
Jak poprawnie używać crontab -e
Crontaba użytkownika nigdy nie edytujesz bezpośrednio w /var/spool/cron/crontabs/, tylko zawsze przez narzędzie:
crontab -e
Przy pierwszym wywołaniu program zgłasza się komunikatem no crontab for root - using an empty one. Na świeżo zainstalowanych systemach pojawia się potem wybór edytora. Jeśli brakuje jakiegokolwiek edytora, na przykład w odchudzonym obrazie, wywołanie przerywa się komunikatem w rodzaju /usr/bin/sensible-editor: 25: editor: not found. Zaradzisz temu tak: zainstaluj edytor albo wskaż wprost ten, którego chcesz użyć.
apt-get install -y nano
EDITOR=nano crontab -e
Dużą zaletą crontab -e wobec bezpośredniego zapisu pliku jest sprawdzenie przy zapisywaniu. Jeśli któryś wiersz jest zepsuty, zobaczysz:
"/tmp/crontab.7hK2mn/crontab":3: bad minute
errors in crontab file, can't install.
Do you want to retry the same edit? (y/n)
Numer wiersza się zgadza, pole w komunikacie już nie zawsze: bad minute pojawia się także wtedy, gdy po prostu brakuje jednego pola, bo parser czyta wówczas wszystko przesunięte w lewo. Przy powodzeniu operacja kończy się komunikatem crontab: installing new crontab. Dopiero ten wiersz oznacza, że zmiana została przyjęta.
Kolejne polecenia, które warto znać:
crontab -l # wyświetlenie
crontab -l > /root/crontab.bak # kopia zapasowa
crontab -u www-data -l # odczyt cudzego crontaba (jako root)
crontab -i -r # usunięcie, z pytaniem (tylko w terminalu)
Uwaga do kopii zapasowej: crontab -l > /root/crontab.bak zgłasza no crontab for root i zwraca kod wyjścia 1 tak długo, jak dla danego użytkownika nie istnieje jeszcze żaden crontab. Plik docelowy i tak powstanie, tyle że pusty, o rozmiarze 0 bajtów. W kolejności opisanej w tym artykule nikt tego nie zauważy, ale w skrypcie, który sprawdza kod wyjścia albo nadpisuje starszą kopię, jak najbardziej.
crontab -r bez -i kasuje cały crontab natychmiast i bez pytania. Ponieważ r i e leżą na klawiaturze niedaleko siebie, jest to realny przypadek utraty danych. W terminalu przyzwyczaj się więc do crontab -i -r, a wcześniej zrób kopię przez crontab -l.
Ważne jest przy tym słowo terminal. crontab -i -r to polecenie czysto interaktywne. Jeśli standardowe wejście nie jest podpięte do terminala, czyli w skrypcie, w cronjobie albo w jednolinijkowcu w rodzaju ssh host "crontab -i -r", pytanie kręci się w nieskończonej pętli: crontab: really delete root's crontab? (y/n) Please enter Y or N: Please enter Y or N: ... powtarza się bez końca i w kilka sekund zapisuje setki kilobajtów wyjścia, a samo wywołanie kończy dopiero przerwanie z zewnątrz. Do wszystkiego, co zautomatyzowane, bierzesz więc wariant nieinteraktywny i robisz wcześniej kopię:
crontab -l > /root/crontab.bak # kopia zapasowa przed usunięciem
crontab -r # usunięcie, bez pytania
printf "y\n" | crontab -i -r # pytanie zostaje, odpowiedź podana z góry
Jeśli istnieją /etc/cron.allow albo /etc/cron.deny, to one decydują, kto w ogóle może zakładać crontaby. Objęci nimi użytkownicy dostają: You (username) are not allowed to use this program (crontab). Gdy /etc/cron.allow jest obecny, obowiązuje wyłącznie on, a każdy niewymieniony użytkownik jest zablokowany.
Crontab użytkownika, /etc/crontab i /etc/cron.d
Harmonogramy mogą stać w czterech miejscach, a każde z nich ma inny format. Właśnie tutaj powstają błędy, które najtrudniej znaleźć.
Crontab użytkownika: pięć pól
Utrzymujesz go przez crontab -e, działa on na koncie użytkownika, do którego należy, i nie ma pola użytkownika. Jeśli mimo to wpiszesz je do środka, cron spróbuje wykonać nazwę użytkownika jako polecenie, a ty znajdziesz w mailu albo w logu:
/bin/sh: 1: root: not found
/etc/cron.d: sześć pól
Pliki w /etc/cron.d/ to crontaby systemowe i mają między polami czasowymi a poleceniem dodatkowe pole użytkownika. To właściwe miejsce na zadania należące do aplikacji albo do systemu zarządzania konfiguracją, bo każdy plik da się wymienić osobno.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
0 3 * * * root /usr/local/bin/backup.sh >> /var/log/kh-backup.log 2>&1
Jeśli zapomnisz pola użytkownika, cron potraktuje pierwsze słowo polecenia jako nazwę użytkownika, a wiersz zawiedzie po cichu, bo taki użytkownik nie istnieje.
Trzy zasady dotyczące /etc/cron.d/ są łamane regularnie:
- Nazwa pliku nie może zawierać kropki. Dozwolone są wyłącznie litery, cyfry, podkreślenia i myślniki.
backup.cronalbokh-backup.shzostaną zignorowane bez słowa komentarza. Powód leży w zarządzaniu pakietami: dzięki temu pozostałości w rodzaju.dpkg-distczy.dpkg-oldnigdy się nie wykonają. Nazwij plik po prostukh-backup. - Uprawnienia i właściciel muszą się zgadzać. Oczekiwane są
root:rootoraz tryb 0644. W przeciwnym razie w logu wylądują komunikaty w rodzaju(*system*) WRONG FILE OWNER,(*system*) BAD FILE MODEalbo wskazówka o niebezpiecznym trybie, zapisywalnym dla grupy lub pozostałych. Zadanie wtedy nie ruszy. - Plik musi kończyć się znakiem nowej linii. cron wymaga, żeby każdy wpis zamykał się znakiem newline. Jeśli ostatni wiersz kończy się bez przełamania, zostaje pominięty, i to bez słowa: w teście porównawczym, na pliku identycznym pod każdym innym względem, ale bez końcowego przełamania, zadanie nie ruszyło ani razu, bez komunikatu błędu i bez wiersza w logu. Kto tworzy plik przez
echo -n, przezprintfbez kończącego\nalbo z szablonu bez pustej linii na końcu, traci dokładnie ostatnie zadanie.
chown root:root /etc/cron.d/kh-backup
chmod 0644 /etc/cron.d/kh-backup
/etc/crontab i katalogi cron.*
/etc/crontab ma również sześć pól i należy do dystrybucji. Zmieniaj go tylko wtedy, gdy wiesz po co. Wywołuje on przez run-parts katalogi /etc/cron.hourly, cron.daily, cron.weekly i cron.monthly. Skrypty w tych katalogach potrzebują bitu wykonywalności i również nie mogą mieć kropki w nazwie. backup.sh w /etc/cron.daily/ nigdy się nie wykona, a backup owszem. Sprawdzisz to bez uruchamiania:
run-parts --test /etc/cron.daily
Wypisane zostaną tylko te skrypty, które run-parts naprawdę by uruchomił. Jeśli twojego nie ma na liście, winna jest nazwa albo bit wykonywalności.
Dlaczego PATH w cronie jest inny
To zdecydowanie najczęstsza przyczyna sytuacji „działa w shellu, ale nie w cronie”. cron nie uruchamia powłoki logowania. Nie są czytane ani ~/.bashrc, ani ~/.profile, ani /etc/profile. Debianowy cron ustawia dla crontabów użytkowników minimalną ścieżkę wyszukiwania:
PATH=/usr/bin:/bin
Brakuje w niej tym samym /usr/local/bin oraz /usr/sbin. Na Debianie 12, Debianie 13, Ubuntu 22.04 i Ubuntu 24.04 /usr/bin i /usr/sbin to nadal osobne katalogi, bo usr-merge objął tylko /bin i /sbin. Wszystko, co sam położyłeś w /usr/local/bin, wszystko z pip install, wszystko z node'a zainstalowanego przez nvm oraz narzędzia systemowe takie jak ufw czy iptables po prostu dla crona nie istnieją. Komunikat błędu brzmi wtedy:
/bin/sh: 1: backup.sh: not found
Druga część pułapki: cron ustawia SHELL=/bin/sh. Na Debianie i Ubuntu /bin/sh to dowiązanie do dash, a nie do bash. Każdy idiom bashowy w nagłówku skryptu albo wprost w wierszu crontaba zawiedzie:
/bin/sh: 1: [[: not found
/bin/sh: 1: source: not found
/bin/sh: 1: Syntax error: "(" unexpected
Trzy środki zaradcze, w tej kolejności:
- Używaj ścieżek bezwzględnych.
/usr/local/bin/backup.shzamiastbackup.sh,/usr/bin/phpzamiastphp. To najodporniejszy wariant, bo działa niezależnie od jakiejkolwiek zmiennej środowiskowej. Ścieżkę jednak odczytaj, zamiast wpisywać ją z pamięci:command -v datezwraca na Debianie 13, Debianie 12, Ubuntu 24.04 i Ubuntu 22.04/usr/bin/date, a na starszych systemach takich jak Debian 11/bin/date. Wiersz z błędną ścieżką crontab przyjmie bez komentarza i bez ostrzeżenia, zawiedzie on dopiero w czasie działania, i to po cichu. Do kontroli razcrontab -l, razenv -i /bin/sh -c "/usr/bin/date", co przy błędnej ścieżce natychmiast zgłosinot found. - Ustaw PATH i SHELL na początku crontaba. Przypisania obowiązują dla wszystkich kolejnych wierszy. Ważne: cron nie rozwija przy tym zmiennych,
PATH=$PATH:/opt/binnie zadziała, wypisz ścieżkę w całości. - Użyj skryptu opakowującego. Wiersz crontaba wywołuje tylko skrypt, a skrypt sam ustawia sobie środowisko.
#!/bin/bash
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd /srv/app
exec ./do-the-work.sh
Jeśli chcesz wiedzieć, co cron naprawdę przekazuje twojemu zadaniu, każ sobie to pokazać. Wpisz na jedną minutę:
* * * * * /usr/bin/env > /tmp/cron-env.txt 2>&1
Tylko ta droga pokazuje środowisko, które cron rzeczywiście ustawia. Shell odtworzony przez env -i jedynie się do niego zbliża, bo przynosi ze sobą własną ścieżkę domyślną. Potem porównaj /tmp/cron-env.txt ze swoim środowiskiem interaktywnym. W oczy rzucają się zwykle nie tylko PATH i SHELL, ale też brakujące zmienne locale. Bez LANG wszystko działa w locale C, co zmienia kolejność sortowania, formaty daty oraz sposób wypisywania znaków diakrytycznych. Skrypty, które opierają się na LANG=de_DE.UTF-8, zachowują się w cronie inaczej. Brakuje też ssh-agenta, dlatego zadania z dostępem po SSH potrzebują klucza bez hasła i wyraźnie podanego -i.
Ostatnia drobna złośliwość w tej kategorii: znak procenta. W wierszu crontaba niezabezpieczony % zamienia się w znak nowej linii, a wszystko po nim trafia do polecenia jako standardowe wejście. Formaty daty trzeba więc zabezpieczyć znakiem ucieczki.
0 2 * * * /usr/bin/tar -czf /backup/web-$(date +\%F).tar.gz /var/www
Przekierowanie wyjścia, maile i „No MTA installed”
Domyślnie cron wysyła mailem do właściciela crontaba wszystko, co zadanie wypisze na stdout albo stderr. Na serwerze bez systemu pocztowego ląduje to w logu:
(CRON) info (No MTA installed, discarding output)
To nie jest błąd zadania. Oznacza tylko, że pojawiło się jakieś wyjście, a nikt nie mógł go odebrać. Ciche zadanie nie generuje tego wiersza. Jest on tym samym nawet użytecznym sygnałem: jeśli nagle się pojawi, twoje zadanie zaczęło coś wypisywać, najczęściej komunikat błędu.
Dla przekierowania są trzy sensowne wzorce:
# wszystko do pliku logu, łącznie z błędami
0 3 * * * /usr/local/bin/backup.sh >> /var/log/kh-backup.log 2>&1
# normalne wyjście do kosza, błędy nadal mailem
0 3 * * * /usr/local/bin/backup.sh > /dev/null
# wszystko do journala, czytelnie oznaczone
0 3 * * * /usr/local/bin/backup.sh 2>&1 | /usr/bin/logger -t kh-backup
Wariant > /dev/null 2>&1 jest popularny i niebezpieczny: wyrzuca również wszystkie komunikaty błędów. Zadanie, które zawodzi od czterech miesięcy, wygląda wtedy dokładnie tak samo jak takie, które działa. Jeśli chcesz zrezygnować z maili, ustaw lepiej MAILTO="" na początku crontaba i zapisuj wyjście do pliku. Jeśli chcesz maile na konkretny adres, ustawiasz MAILTO=alerts@example.org, ale potrzebujesz do tego zainstalowanego systemu pocztowego.
Własne pliki logów w /var/log/ rosną bez ograniczeń. Załóż dla nich małą regułę w /etc/logrotate.d/, bo inaczej najskuteczniejszy cronjob kiedyś zapcha ci dysk.
Po czym poznasz, że zadanie naprawdę się wykonało
Tutaj szybki poradnik rozchodzi się z wiarygodnym sprawdzeniem. cron zapisuje w logu start zadania. Nie zapisuje ani jego zakończenia, ani kodu wyjścia. Wiersz CMD w logu dowodzi więc tylko tego, że cron uruchomił shell, a nie tego, że twój skrypt zakończył się powodzeniem.
Logi czytasz różnie, zależnie od systemu:
journalctl -u cron --since "30 min ago"
journalctl -t CRON --since today
Na Debianie 12 i Debianie 13 to jedyna droga, bo od wydania Bookworm rsyslog nie jest już instalowany domyślnie. Plik /var/log/syslog z reguły tam nie istnieje. Kto go szuka i nie znajduje, błędnie uzna, że zadanie w ogóle nie wystartowało.
Na Ubuntu 22.04 i 24.04 rsyslog jest zwykle obecny, więc dodatkowo zadziała tam:
grep CRON /var/log/syslog
Osobnego pliku /var/log/cron.log nie ma fabrycznie w żadnej z czterech wersji. Odpowiednia reguła w /etc/rsyslog.d/50-default.conf jest zakomentowana. Nie szukaj tego pliku, istnieje on tylko wtedy, gdy ktoś go świadomie włączył.
Czysty dowód pochodzi więc z samego zadania. Każ skryptowi zapisać na końcu znacznik czasu oraz kod wyjścia:
#!/bin/bash
set -euo pipefail
trap 'echo "$(date -Is) kh-backup zakończony, exit $?" >> /var/log/kh-backup.log' EXIT
# właściwa praca
Masz w ten sposób trzy dowody zamiast jednego: wiersz CRON w journalu (cron wystartował), wiersz końcowy w twoim pliku logu (skrypt doszedł do końca) oraz kod wyjścia (zakończył się czysto). Dopiero gdy zgadzają się wszystkie trzy, zadanie działa.
Jeśli zadanie może działać dłużej niż jego interwał, zabezpiecz je dodatkowo przed nakładaniem się. Inaczej w pewnym momencie ruszy równolegle dziesięć instancji i położą ci serwer:
*/5 * * * * /usr/bin/flock -n /var/lock/kh-sync.lock /usr/local/bin/sync.sh
flock -n kończy działanie natychmiast, jeśli jedna instancja już działa. Narzędzie siedzi w pakiecie util-linux i jest obecne we wszystkich czterech systemach.
@reboot i dlaczego systemd jest zwykle lepszy
Za pomocą @reboot da się wykonać polecenie przy starcie systemu. Obok tego są jeszcze @daily, @hourly, @weekly, @monthly i @yearly, z których każdy zastępuje pięć pól czasowych.
@reboot /usr/local/bin/start-app.sh
Wygląda to wygodnie, ale ma trzy poważne słabości:
- Ten moment to nie „po starcie systemu”, tylko „gdy startuje cron”. To, czy sieć, baza danych albo zamontowany zasób są wtedy gotowe, jest czystą loterią. Zwykłym prowizorium jest
sleep 30na początku, co problem jedynie przesuwa. - Restart usługi cron wyzwala @reboot ponownie.
systemctl restart cron, choćby po aktualizacji pakietów, uruchomi twoją aplikację po raz drugi, mimo że pierwsza wciąż działa. - Nie ma żadnego nadzoru. Ani kodu wyjścia, ani restartu po awarii, ani statusu.
Do wszystkiego, co ma działać na stałe, należy się usługa systemd, a nie cronjob. Do zadań cyklicznych lepszym wyborem jest timer. Wystarczą dwa pliki:
[Unit]
Description=KernelHost Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
[Unit]
Description=KernelHost Backup codziennie
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=900
[Install]
WantedBy=timers.target
Włączasz timer, a nie usługę:
systemctl daemon-reload
systemctl enable --now kh-backup.timer
systemctl list-timers kh-backup.timer
Trzy decydujące przewagi nad cronem: Persistent=true nadrabia pominięte uruchomienie przy następnym starcie, a cron pomija je bez słowa. Kod wyjścia trafia do systemctl status kh-backup.service, widzisz więc bez własnego logowania, czy się udało. Całe wyjście masz do dyspozycji przez journalctl -u kh-backup.service, bez przekierowań i bez maila. Ścieżka wyszukiwania jest zresztą w systemd hojniejsza niż w cronie i zawiera domyślnie także /usr/local/bin, ale zupełnie bez własnych dodatków i tak pozostaje skromna.
W zadaniach startowych zastępujesz @reboot usługą z jasno określonymi zależnościami:
[Unit]
After=network-online.target
Wants=network-online.target
Dzięki temu twoje zadanie ruszy gwarantowanie dopiero wtedy, gdy sieć faktycznie stoi. Więcej o unitach i ich budowie znajdziesz w artykule tworzenie usługi systemd.
Strefa czasowa, UTC i czas letni
cron liczy zawsze w systemowej strefie czasowej z /etc/localtime. Na wielu serwerach i w niemal wszystkich obrazach chmurowych jest to UTC. Zadanie ustawione na 0 3 * * * ruszy wtedy latem o 05:00 czasu środkowoeuropejskiego letniego, a nie o 03:00. Sprawdź najpierw, z czym masz do czynienia:
readlink -f /etc/localtime
timedatectl show -p Timezone --value
date
date -u
readlink -f /etc/localtime podaje ścieżkę w /usr/share/zoneinfo, a tym samym strefę faktycznie obowiązującą, na przykład /usr/share/zoneinfo/Etc/UTC albo /usr/share/zoneinfo/Europe/Vienna. Działa na wszystkich czterech wersjach, także tam, gdzie systemd nie jest uruchomiony. timedatectl show -p Timezone --value zwraca tę samą informację krótko i w formie czytelnej dla maszyny, wymaga jednak systemd.
Czego warto się oduczyć, to zaglądania do /etc/timezone. Debian 13 nie dostarcza już tego pliku, cat /etc/timezone kończy się tam komunikatem cat: /etc/timezone: No such file or directory, a instalacja tzdata również go nie przywraca. Na Debianie 12, Ubuntu 24.04 i Ubuntu 22.04 plik jeszcze istnieje, więc odpowiedź wypada różnie w zależności od wersji. Rozstrzygające jest w każdym przypadku wyłącznie dowiązanie /etc/localtime: wartość wpisana ręcznie do /etc/timezone nie zmienia systemowej strefy czasowej, a tym samym nie zmienia też momentu wyzwolenia twoich zadań.
Systemową strefę czasową możesz przestawić w każdej chwili, a potem zrestartuj crona, żeby usługa na pewno przejęła zmianę:
timedatectl set-timezone Europe/Vienna
systemctl restart cron
Na systemach bez działającego systemd ustawiasz zamiast tego dowiązanie bezpośrednio, wynik jest ten sam i da się go od razu zweryfikować przez readlink -f /etc/localtime:
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
Teraz punkt, który w wielu poradnikach jest opisany błędnie: cron Debiana i Ubuntu nie zna zmiennej CRON_TZ. Ta zmienna pochodzi z cronie, czyli crona z Red Hata, Fedory i AlmaLinuksa. Na Debianie 13, Debianie 12, Ubuntu 24.04 i Ubuntu 22.04 nie istnieje strefa czasowa dla pojedynczego użytkownika ani dla pojedynczego crontaba. Jeśli wstawisz tam do crontaba TZ=Europe/Vienna, zadziała to wyłącznie na środowisko wykonywanych poleceń, więc date w skrypcie pokaże czas wiedeński. Sam moment wyzwolenia pozostaje przez to całkowicie nietknięty i nadal kieruje się systemową strefą czasową. Kto to przeoczy, ma zadanie pozornie skonfigurowane poprawnie, które i tak startuje dwie godziny obok.
Zostają trzy czyste drogi: ustawić odpowiednio systemową strefę czasową, samodzielnie przeliczyć godziny na UTC albo przejść na timer systemd, który przyjmuje strefę czasową wprost w harmonogramie. Sprawdzisz to bez ryzyka:
systemd-analyze calendar "Mon..Fri 03:00 Europe/Vienna"
Wyjście podaje najbliższe wyzwolenie jako konkretną datę. To najbardziej niezawodna kontrola, jaką możesz przeprowadzić przed włączeniem zadania.
Do czasu letniego jeszcze praktyczna rada: nie planuj zadań między 02:00 a 03:00. W marcu ta godzina nie istnieje, w październiku istnieje dwa razy. Zależnie od zadania oznacza to uruchomienie pominięte albo podwojone, jedno i drugie raz w roku i jedno i drugie trudne do odtworzenia. 01:30 albo 03:30 to alternatywy, które nie sprawiają kłopotów. Przy timerach systemd pomaga dodatkowo Persistent=true, dzięki czemu pominięte uruchomienie zostaje nadrobione.
Szybka diagnostyka w siedmiu krokach
Jeśli cronjob nie działa, przerób tę listę po kolei. Pokrywa ona praktycznie wszystkie przypadki.
- Czy usługa działa?
systemctl status cron. Nie ma usługi, nie ma zadania. - Czy wpis w ogóle dotarł?
crontab -ldla zadań użytkownika, w pozostałych przypadkach zajrzyj do pliku w/etc/cron.d/. Sprawdź nazwę pliku bez kropki, tryb 0644, właściciela root oraz końcowy znak nowej linii. - Czy cron w ogóle je uruchomił?
journalctl -t CRON --since today. Jeśli brakuje wiersza CMD, problemem jest harmonogram albo plik, a nie skrypt. - Czy liczba pól się zgadza? Pięć pól w crontabie użytkownika, sześć w
/etc/cron.di/etc/crontab. - Ścieżki bezwzględne? Każde polecenie i każdy skrypt wpisuj z pełną ścieżką, a samą ścieżkę ustal wcześniej przez
command -v, zamiast wpisywać ją z głowy. - Środowisko sprawdzone? Wpisz jednorazowo
* * * * * /usr/bin/env > /tmp/cron-env.txt 2>&1, odczekaj minutę, porównaj. - Testuj w kontekście crona. Nie uruchamiaj skryptu w swoim shellu, tylko z pustym środowiskiem:
env -i PATH=/usr/bin:/bin /bin/sh -c '/usr/local/bin/backup.sh'. Ścieżkę wyszukiwania podajesz przy tym świadomie. Samoenv -i /bin/shwprawdzie czyści środowisko, ale dash ustawia sobie potem własną ścieżkę domyślną/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, a ta jest hojniejsza niż ścieżka crona. Dokładnie te błędy, o które tu chodzi, pozostałyby w ten sposób nieodkryte. Jeśli wywołanie zawiedzie, masz przyczynę, i to bez czekania na kolejny moment wyzwolenia.
Ostatni punkt jest najcenniejszy. Niemal każdy cronjob, który „w niewyjaśniony sposób” nie działa, zawodzi w sposób powtarzalny, gdy tylko uruchomi się go z pustym środowiskiem. Zagadka pojawiająca się raz na 24 godziny zamienia się w ten sposób w błąd, który usuniesz w dziesięć sekund.
Kto regularnie robi backupy cronjobem, powinien dodatkowo zabezpieczyć system docelowy. Pasujące wskazówki znajdziesz w naszym wpisie o zabezpieczaniu serwera z Linuksem.
Najczęstsze pytania
Dlaczego mój skrypt działa w shellu, ale nie jako cronjob?
Co oznacza komunikat No MTA installed, discarding output?
Czy Debian albo Ubuntu obsługuje zmienną CRON_TZ?
Dlaczego mój plik w /etc/cron.d jest ignorowany?
Gdzie znajdę logi crona na Debianie 12 i 13?
Czy wiersz CMD w logu dowodzi, że zadanie zakończyło się powodzeniem?
Lepiej użyć @reboot czy usługi systemd?
2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

