Dlaczego zadanie cron nie uruchamia się
Poznaj pięć najczęstszych przyczyn braku wykonania zadań w cron. Sprawdź minimalne zmienne PATH, problematyczne znaki procenta, błędne pliki crontab oraz brak przekierowania wyjścia.
Dlaczego zadanie cron nie uruchamia się
Zadanie cron, które „nigdy się nie uruchamia”, prawie zawsze zostało uruchomione. Wykonało się w środowisku innym niż powłoka użytkownika, zakończyło się niepowodzeniem w pierwszej sekundzie, a komunikat o błędzie trafił w miejsce, którego nie monitorujesz. Pięć przyczyn wyjaśnia niemal każde zgłoszenie: ścieżka wyszukiwania (PATH), znak procenta, niewłaściwy plik crontab, wyjście przekierowane do poczty oraz skrypt oczekujący sesji logowania.
cron to demon (usługa działająca w tle), który odczytuje pliki crontab i uruchamia polecenia zgodnie z harmonogramem. Nie odczytuje on pliku .bashrc, nie otwiera terminala, nie inicjuje powłoki logowania i nie informuje o niepowodzeniu polecenia. Każda z poniższych przyczyn wynika z tych czterech faktów.
Przeanalizuj je w podanej kolejności, zaczynając od pytania nadrzędnego: czy cron w ogóle wywołał zadanie? „cron nigdy nie uruchomił zadania” oraz „zadanie uruchomiło się i zakończyło błędem” to odrębne problemy, które nie mają ze sobą nic wspólnego, więc odpowiedz na to pytanie w pierwszej kolejności.
Czy cron w ogóle został uruchomiony?
Demon posiada różne nazwy jednostek w zależności od rodziny dystrybucji. Należy sprawdzić obie, a następnie odczytać dziennik.
systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"W systemach Debian i Ubuntu jednostka nazywa się cron. W systemach Fedora, Rocky oraz Alma nosi nazwę crond. Na danej maszynie istnieje tylko jedna z tych nazw, więc komunikat o nieznanej jednostce dla jednego z tych poleceń jest zjawiskiem normalnym i nie świadczy o błędzie.
Należy odczytać wpisy wygenerowane przez własny system. Nie należy szukać linii skopiowanej z poradnika, ponieważ sformułowania różnią się w zależności od implementacji cron oraz konfiguracji logowania. Sprawdzane są tylko dwie rzeczy: czy istnieje wpis z minutą określoną w harmonogramie oraz czy wpis ten zawiera nazwę polecenia. Wpis zawierający nazwę polecenia oznacza, że cron wykonał swoje zadanie, a przyczyna błędu leży wewnątrz samego polecenia. Brak wpisu oznacza, że cron nie otrzymał harmonogramu, co odpowiada przyczynie nr 3 poniżej.
Niektóre obrazy systemowe przesyłają komunikaty cron przez rsyslog do pliku zamiast do journal. Należy sprawdzić katalog /var/log pod kątem pliku o nazwie cron lub syslog, a następnie odczytać jego końcową część.
ls -l /var/log
sudo tail -n 50 /var/log/syslogJeśli nie istnieje ani jednostka, ani dziennik, cron może być po prostu niezainstalowany. Minimalne obrazy chmurowe oraz kontenery często go pomijają.
dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cronPrzyczyna 1: cron nie posiada Twojej zmiennej PATH
Interaktywna powłoka buduje PATH na podstawie /etc/profile, ~/.profile, ~/.bashrc oraz wszystkich plików, które są przez nie wczytywane. Żaden z tych mechanizmów nie działa w przypadku zadania cron. cron uruchamia polecenie w swoim własnym, ograniczonym środowisku, dlatego programy znajdujące się poza standardowymi katalogami systemowymi nie są odnajdywane. Dotyczy to wszystkiego, co znajduje się w /usr/local/bin, /opt, menedżerów wersji języków, wirtualnych środowisk Python czy obszarów roboczych Go. Zadanie kończy się niepowodzeniem w pierwszej linii, a powłoka generuje błąd typu "not found", którego dokładna treść zależy od użytej powłoki.
Znajdź pełną ścieżkę do każdego polecenia używanego w zadaniu.
command -v docker
command -v node
readlink -f "$(command -v node)"Następnie wpisz te bezwzględne ścieżki bezpośrednio w zadaniu lub ustaw PATH raz na początku pliku crontab.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool runPobierz tę listę ze swojej maszyny za pomocą echo "$PATH" i usuń wszystko, co istnieje tylko w ramach sesji interaktywnej. Obowiązuje jedna zasada: cron nie rozwija zmiennych w tych liniach przypisania. PATH=$PATH:/usr/local/bin przechowuje dosłowny tekst $PATH:/usr/local/bin, więc zadanie kończy się ścieżką wyszukiwania, która nie zawiera żadnego użytecznego katalogu. Wpisz pełną listę.
Menedżer wersji wymaga czegoś więcej niż tylko ścieżki. nvm, pyenv, rbenv oraz asdf instalują funkcję powłoki lub katalog shims z Twojego .bashrc, a zadanie cron nigdy nie odczytuje tego pliku. Wywołuj wersjonowany plik binarny przez ścieżkę bezwzględną lub wczytaj skrypt inicjujący menedżera jako pierwszą linię własnego skryptu.
Przyczyna 2: znak procenta kończy polecenie
W polu polecenia pliku crontab znak % nie jest zwykłym znakiem. Pierwszy niepoprzedzony znakiem ucieczki % kończy polecenie. Wszystko, co znajduje się za nim, jest przekazywane do polecenia jako standardowe wejście, a każdy kolejny % staje się znakiem nowej linii. Jest to rzeczywista funkcja crona służąca do przekazywania krótkich danych wejściowych do programu i właśnie dlatego nazwa pliku z datą jest klasycznym przykładem błędnego wpisu w crontab.
Zapis 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site sprawia, że tar nigdy nie otrzyma sformatowanej daty. cron ucina linię w miejscu pierwszego %, więc powłoka otrzymuje niedokończone podstawienie polecenia, a reszta linii trafia na standardowe wejście. Każdy znak procenta należy poprzedzić znakiem odwrotnego ukośnika.
0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/siteDwie warstwy odczytują tę linię w określonej kolejności. \% to reguła crona, stosowana przez crona przed uruchomieniem czegokolwiek. $(date +\%F) to podstawienie polecenia, stosowane później przez powłokę uruchamianą przez crona. Zrozumienie, która warstwa odpowiada za dany znak, stanowi klucz do rozwiązania problemu.
Bezpieczniejszym nawykiem jest całkowite wyprowadzenie logiki poza plik crontab. Należy umieścić ją w skrypcie, w którym znak procenta nie posiada żadnego specjalnego znaczenia.
#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/siteLinia w crontab zawiera wtedy tylko ścieżkę i przekierowanie. Crontab, który można przejrzeć jednym spojrzeniem, to crontab, który można łatwo debugować.
Cause 3: which crontab did you edit?
There is no single crontab. There are several files, with different owners and different field counts, and a job written into the wrong one is invisible.
crontab -eedits the crontab of the user who runs the command.sudo crontab -eedits root's. Two people debugging the same box often end up reading two different files.sudo crontab -l -u deploylists another user's crontab, which is how you confirm what is actually installed for the account that should run the job./etc/crontaband every file in/etc/cron.dcarry one extra field between the schedule and the command: the user to run as. Paste a five-field user crontab line into/etc/cron.dand the first word of your command is read as a username.- Files in
/etc/cron.dmust be named with letters, digits, underscores and hyphens. A file calledbackup.shorsite.confis skipped because of its name alone. Rename it tobackupand check your log again. - Files in
/etc/cron.dshould be owned by root, and must not be writable by group or others.ls -l /etc/cron.dshows you both facts at once. - Scripts dropped into
/etc/cron.dailyand its siblings follow the same naming rule and must also carry the execute bit. A missing execute bit is a silent skip. /etc/cron.allowand/etc/cron.denydecide who may install a crontab at all. If either exists on your box, read it before assuming your user is allowed one.
Install a user crontab with the crontab command instead of editing the spool file by hand, because crontab parses the file before installing it. When you save, read what the command prints back to you. If it refuses the file, the previous version stays live and your change never took effect, which looks exactly like cron ignoring you.
The owner also decides permissions. A job in root's crontab creates root-owned files that the application reading them may not be able to write. A job in a normal user's crontab cannot read a root-only directory. Match the owner to the work: application maintenance belongs to the application's own account, which is the reasoning behind replacing WordPress wp-cron with a system cron job. The mode of the files your job creates comes from the umask it inherits, and that is another value which is not your shell's, so how umask sets file permissions is worth reading if a job's output lands unreadable.
Przyczyna 4: dane wyjściowe trafiają na nieczytaną skrzynkę pocztową
cron przechwytuje wszystko, co zadanie zapisuje na standardowe wyjście i standardowe wyjście błędów. Jeśli zadanie wygenerowało jakikolwiek tekst, cron przekazuje go do lokalnego systemu pocztowego, adresując wiadomość do właściciela pliku crontab lub użytkownika wskazanego przez MAILTO. Na okrojonym serwerze VPS zazwyczaj nie ma zainstalowanego żadnego MTA (mail transfer agent), więc wiadomość nie jest dostarczana. Błąd istniał przez chwilę, po czym zniknął. To główny powód, dla którego niedziałające zadanie wydaje się nie generować żadnych komunikatów.
Przekieruj dane wyjściowe do pliku, do którego masz dostęp.
0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1>> dopisuje standardowe wyjście do pliku. 2>&1 kieruje standardowe wyjście błędów tam, gdzie w danej chwili wskazuje standardowe wyjście, dlatego musi znajdować się po przekierowaniu. Zapisane w odwrotnej kolejności, jako 2>&1 >> file, sprawi, że standardowe wyjście błędów zachowa swoje pierwotne miejsce docelowe, a błąd, którego szukasz, będzie dokładnie tą częścią, która nigdy nie trafi do pliku.
Dziennik systemowy to kolejne dobre miejsce. logger zapisuje dane do syslog pod wybranym znacznikiem.
0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-siteOdczytaj go za pomocą journalctl -t backup-site. Pozwala to zachować dane wyjściowe zadania obok wpisów w cron, co ułatwia śledzenie osi czasu. Jeśli potrzebujesz również rejestru tego, który użytkownik wykonał konkretne polecenie na serwerze, jest to zadanie dla osobnego systemu, co opisano w audytowanie poleceń użytkowników na serwerze.
MAILTO="" na początku pliku crontab wyłącza wysyłanie poczty dla zadań znajdujących się poniżej. Ustawienie MAILTO na poprawny adres e-mail pomaga tylko wtedy, gdy w systemie działa MTA, dlatego przed poleganiem na tym rozwiązaniu upewnij się, że poczta faktycznie opuszcza serwer.
Jedna zasada podczas debugowania: nigdy nie dopisuj > /dev/null 2>&1. Jest to najpopularniejsza linia w każdym pliku crontab, która niszczy jedyne dowody, jakimi dysponujesz. Przywróć ją później, gdy zadanie będzie już działać poprawnie.
Przyczyna 5: skrypt zakłada istnienie środowiska, którego cron nie zapewnia
Gdy polecenie zostanie znalezione, a jego wyjście przechwycone, pozostaje wszystko to, co sesja użytkownika udostępnia automatycznie.
- Powłoka może nie być bash. Sprawdź to za pomocą
ls -l /bin/sh. W systemach Debian i Ubuntu wskazuje ona na dash, więc testy z podwójnym nawiasem, tablice orazsourcekończą się błędem składni. Dodaj do skryptu linię#!/bin/bashi wywołuj go bezpośrednio lub ustawSHELLna początku pliku crontab. - Katalog roboczy nie jest tym, w którym aktualnie przebywasz. Używaj wszędzie ścieżek bezwzględnych lub wykonaj
cddo odpowiedniego katalogu w pierwszej linii skryptu. Ścieżka względna to najczęstsza przyczyna, dla której zadanie „działa, gdy uruchamiam je ręcznie”. - Ustawienia regionalne (locale) różnią się od tych z Twojej sesji. Wszystko, co formatuje datę, liczby lub sortuje tekst, może generować inne wyniki przy innym
LANG. Jeśli późniejszy krok analizuje to wyjście, ustaw locale bezpośrednio w skrypcie, zamiast polegać na domyślnych wartościach. - Brak TTY (terminala). Polecenie wymagające potwierdzenia, otwierające edytor lub rysujące pasek postępu może zawiesić się lub zakończyć działanie. Dodaj odpowiednią flagę trybu nieinteraktywnego, jeśli narzędzie ją oferuje.
- Brak agenta SSH.
SSH_AUTH_SOCKnie znajduje się w środowisku crona, więc poleceniesshlubrsync, które działało dzięki załadowanemu agentowi, teraz nie przejdzie uwierzytelnienia. Wygeneruj dla zadania osobny klucz, którego właścicielem będzie użytkownik wykonujący zadanie. - Brak magistrali sesji użytkownika, więc
systemctl --userwywołane z crona kończy się niepowodzeniem, dopóki nie zostanie ustawioneXDG_RUNTIME_DIR. Lepszym rozwiązaniem jest użycie jednostki systemowej (system unit).
W systemach Fedora, Rocky i Alma istnieje jeszcze jeden podejrzany. SELinux ogranicza zadania crona, więc próba uzyskania dostępu do ścieżki z nieoczekiwaną etykietą zostanie odrzucona, nawet jeśli uprawnienia do pliku wydają się poprawne. Sprawdź odmowy dostępu za pomocą sudo ausearch -m avc -ts recent i przeczytaj podstawy SELinux dla serwera przed wyłączeniem jakichkolwiek zabezpieczeń.
Jednominutowa sonda diagnostyczna środowiska cron
Zamiast zgadywać, co zawiera środowisko cron, należy je odczytać. W tym celu należy utworzyć skrypt zrzucający wszystkie zmienne, zaplanować jego uruchamianie co minutę, odczekać chwilę, a następnie odczytać plik wynikowy.
cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.shNależy dodać jeden wiersz do pliku crontab użytkownika, na którego koncie wykonywane jest właściwe zadanie, używając ścieżek bezwzględnych po obu stronach.
* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1Po upływie minuty należy odczytać plik /home/deploy/cron-probe.log i porównać jego zawartość z wynikami tych samych poleceń uruchomionych w bieżącej powłoce. Linia PATH, bieżący katalog roboczy oraz ustawienia regionalne (locale) zazwyczaj samodzielnie wyjaśniają przyczynę niepowodzenia. Należy zwrócić uwagę na dwa szczegóły konfiguracji: znaki procenta znajdują się wewnątrz skryptu, gdzie reguły cron nie mają zastosowania, a ścieżka do logu wskazuje na miejsce, do którego użytkownik zadania posiada uprawnienia zapisu.
Po uzyskaniu odpowiedzi należy niezwłocznie usunąć wpis z crontab. Zadanie uruchamiane co minutę, które dopisuje dane do pliku, może zapełnić mały dysk, czyniąc to w sposób niezauważalny.
Czy harmonogram jest tym, o który chodziło?
Wiersz w pliku crontab użytkownika rozpoczyna się od pięciu pól: minuta, godzina, dzień miesiąca, miesiąc, dzień tygodnia. Dwa z nich współdziałają w sposób zaskakujący dla wielu osób.
Gdy zarówno dzień miesiąca, jak i dzień tygodnia są ograniczone, co oznacza, że żaden z nich nie jest *, cron uruchamia zadanie, gdy którekolwiek z tych pól pasuje. 0 0 13 * 5 nie oznacza "piątku trzynastego". Zadanie zostanie uruchomione o północy trzynastego dnia każdego miesiąca oraz o północy w każdy piątek. Aby uzyskać konkretny dzień, należy pozostawić jedno z tych dwóch pól jako *, a drugie przetestować wewnątrz skryptu.
cron korzysta ze strefy czasowej systemu. Wiele obrazów VPS jest domyślnie ustawionych na UTC (uniwersalny czas koordynowany), więc zadanie zaplanowane na 03:00 uruchomi się o 03:00 UTC, co może przypadać na środek popołudnia w Twojej lokalizacji. timedatectl wyświetla strefę czasową używaną przez serwer. Sprawdź własne ustawienia, zamiast zakładać, że są one zgodne z ustawieniami Twojego laptopa.
Warto znać jeszcze dwie pułapki związane z harmonogramem. @reboot uruchamia się w momencie startu samego demona cron, co nie jest tożsame z momentem uzyskania gotowości sieci. Zadanie wymagające DNS lub połączenia ze zdalnym hostem może zatem zakończyć się niepowodzeniem podczas rozruchu, mimo że będzie działać poprawnie przy każdym ręcznym wywołaniu. Ponadto nic nie blokuje ponownego uruchomienia wolnego zadania, jeśli poprzednia kopia nadal pracuje. Należy zabezpieczyć je za pomocą blokady.
*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1flock -n kończy działanie natychmiast, gdy blokada jest już zajęta, dzięki czemu nakładające się uruchomienia zostają przerwane zamiast kumulować się jedno na drugim.
Kiedy timer systemd jest lepszym narzędziem
cron sprawdza się w jednym zadaniu: uruchomieniu polecenia o określonej godzinie. W każdym innym aspekcie jest niewystarczający. Timer zapewnia dostęp do dziennika bez konieczności przekierowań, kod wyjścia, który można później sprawdzić, kolejkowanie względem network-online.target oraz losowe opóźnienie, dzięki któremu sto serwerów nie uruchamia zadań w tej samej sekundzie. Gdy zadanie wymaga którejkolwiek z tych funkcji, usługa i timer systemd na VPS wymagają mniej pracy niż utrzymywanie wpisu w crontab. Mechanizmy ponawiania prób również powinny być obsługiwane w ten sposób, ponieważ polityki restartu systemd określają zachowanie po wystąpieniu błędu, podczas gdy cron nie oferuje w tym zakresie żadnych rozwiązań.
Pozostaw crona do prostych zadań. Przenieś wszystko, co posiada zależności lub wymaga polityki ponawiania prób, do timera. Oba rozwiązania mogą działać na tym samym serwerze, więc nie jest to migracja, którą trzeba przeprowadzić jednorazowo.
FAQ
Dlaczego zadanie cron działa ręcznie, a nie działa z poziomu cron?
Ponieważ środowisko Twojej powłoki różni się od środowiska cron. Powłoka logowania odczytuje /etc/profile oraz ~/.bashrc, które ustawiają PATH, ustawienia regionalne oraz zmienne agenta. cron uruchamia polecenie bez tych ustawień, z innego katalogu roboczego i czasami przy użyciu innej powłoki. Używaj ścieżek bezwzględnych dla każdego polecenia, ustawiaj wymagane zmienne na początku pliku crontab lub wewnątrz skryptu. Zaplanuj zadanie testowe uruchamiane co minutę, które zapisuje wyniki env | sort, pwd oraz id do pliku dziennika, aby poznać rzeczywiste środowisko cron zamiast zgadywać.
Jak sprawdzić, czy cron faktycznie wykonał moje zadanie?
Odczytaj dziennik demona. Użyj journalctl -u cron w systemach Debian i Ubuntu lub journalctl -u crond w systemach Fedora, Rocky i Alma; niektóre obrazy przekierowują komunikaty przez rsyslog do pliku w /var/log. Szukaj wpisu z minutą odpowiadającą harmonogramowi i sprawdź, czy zawiera on Twoje polecenie. Brak wpisu oznacza, że cron nie posiadał takiego harmonogramu, więc zweryfikuj, czy edytowano właściwy plik crontab. Wpis bez rezultatu oznacza, że polecenie zostało uruchomione i zakończyło się, więc przechwyć jego wyjście za pomocą przekierowania.
Dlaczego date +%Y powoduje błędy wewnątrz crontab?
cron traktuje % jako znak specjalny w polu polecenia. Pierwszy niepoprzedzony znakiem ucieczki % kończy polecenie, wszystko co po nim następuje jest przekazywane do tego polecenia jako standardowe wejście, a każdy kolejny % staje się znakiem nowej linii. W rezultacie nazwa pliku sformatowana datą nigdy nie dociera do programu, dla którego została przeznaczona. Poprzedź każdy znak procenta znakiem ucieczki \% lub przenieś polecenie do skryptu i wywołuj skrypt z cron, ponieważ wewnątrz skryptu znak procenta nie ma specjalnego znaczenia.
Gdzie trafia wyjście zadania cron?
Do lokalnego systemu pocztowego, adresowanego do właściciela pliku crontab lub na adres wskazany przez MAILTO. Większość obrazów VPS nie ma zainstalowanego agenta przesyłania poczty, więc wiadomość jest odrzucana, a zadanie wydaje się nie generować żadnego wyjścia. Przekieruj wyjście do pliku za pomocą >> /path/to/log 2>&1, zachowując tę kolejność, aby standardowe wyjście błędów podążało za standardowym wyjściem, lub przekaż je przez logger -t myjob i odczytaj za pomocą journalctl -t myjob. Nie używaj > /dev/null 2>&1 podczas debugowania.
Czy powinienem używać cron czy systemd timer?
Używaj cron do prostych poleceń uruchamianych o stałej porze, zwłaszcza jeśli istnieje możliwość przeniesienia ich na maszynę, która nie korzysta z systemd. Używaj timer, gdy potrzebujesz wyjścia w dzienniku bez konieczności przekierowań, możliwości odpytania o status zakończenia, uruchomienia po nawiązaniu połączenia sieciowego, losowego opóźnienia startu lub polityki ponawiania po awarii. Oba rozwiązania mogą działać na tym samym serwerze, więc zadania można przenosić pojedynczo, gdy zajdzie taka potrzeba.