SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-09-04

Dlaczego zadanie cron nie uruchamia się

Poznaj pięć najczęstszych przyczyn problemów z cron. Dowiedz się, jak zdiagnozować błędy PATH, nieobsługiwane znaki procenta oraz dlaczego skrypty wymagają pełnych ścieżek.

Dlaczego zadanie cron nie uruchamia się

Zadanie cron, które „nigdy się nie uruchamia”, niemal zawsze zostało wywołane. Uruchomił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żdy taki przypadek: ścieżka wyszukiwania, znak procenta, niewłaściwy plik crontab, wyjście przekierowane do poczty oraz skrypt oczekujący sesji logowania.

cron to daemon (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 uruchamia powłoki logowania i nie informuje o błędach wykonania 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, dlatego 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 sytuacja, w której jedno z dwóch poleceń zgłasza nieznaną jednostkę, jest normalna i nie stanowi błędu.

Należy odczytać wpisy zapisane 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 błąd leży wewnątrz samego polecenia. Brak wpisu oznacza, że cron nie posiadał harmonogramu, co jest przyczyną nr 3 opisaną poniżej.

Niektóre obrazy 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ą zawartość.

ls -l /var/log
sudo tail -n 50 /var/log/syslog

Jeśli ani jednostka, ani dziennik nie istnieją, cron może nie być zainstalowany. Minimalne obrazy chmurowe oraz kontenery często go nie zawierają.

dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cron

Przyczyna 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 procesów nie zachodzi podczas wykonywania 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 w /usr/local/bin, /opt, menedżerów wersji języków programowania, wirtualnych środowisk Python czy przestrzeni 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żytego interpretera.

Znajdź pełną ścieżkę 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 jednorazowo na początku pliku crontab.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool run

Pobierz tę listę ze swojej maszyny za pomocą echo "$PATH" i usuń z niej wszystko, co istnieje wyłącznie 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 binaria w konkretnych wersjach poprzez ścieżkę bezwzględną lub wczytaj skrypt inicjalizują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ę po 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 dlatego wpis z nazwą pliku zawierającą 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 powoduje, ż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/site

Dwie 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, jest kluczem 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 specjalnego znaczenia.

#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/site

Linia w crontab zawiera wtedy tylko ścieżkę i przekierowanie. Przejrzysty plik crontab to plik, 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 -e edits the crontab of the user who runs the command. sudo crontab -e edits root's. Two people debugging the same box often end up reading two different files.
  • sudo crontab -l -u deploy lists another user's crontab, which is how you confirm what is actually installed for the account that should run the job.
  • /etc/crontab and every file in /etc/cron.d carry one extra field between the schedule and the command: the user to run as. Paste a five-field user crontab line into /etc/cron.d and the first word of your command is read as a username.
  • Files in /etc/cron.d must be named with letters, digits, underscores and hyphens. A file called backup.sh or site.conf is skipped because of its name alone. Rename it to backup and check your log again.
  • Files in /etc/cron.d should be owned by root, and must not be writable by group or others. ls -l /etc/cron.d shows you both facts at once.
  • Scripts dropped into /etc/cron.daily and its siblings follow the same naming rule and must also carry the execute bit. A missing execute bit is a silent skip.
  • /etc/cron.allow and /etc/cron.deny decide 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 trafiły na pocztę, której nikt nie czyta

cron zbiera wszystko, co zadanie zapisuje do standardowego wyjścia i standardowego błędu. Jeśli zadanie wygenerowało jakikolwiek tekst, cron przekazuje go do lokalnego systemu pocztowego, adresując wiadomość do właściciela crontab lub do odbiorcy wskazanego przez MAILTO. Na okrojonym VPS zazwyczaj nie ma zainstalowanego MTA (mail transfer agent), więc wiadomość nie jest dostarczana. Błąd istniał przez chwilę, a następnie zniknął. To główny powód, dla którego niedziałające zadanie wydaje się nie generować żadnych komunikatów.

Przekieruj wyjście 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 standardowy błąd tam, gdzie w danej chwili wskazuje standardowe wyjście, dlatego musi znajdować się po przekierowaniu. Zapisane w odwrotnej kolejności, jako 2>&1 >> file, sprawia, że standardowy błąd zachowuje swoje pierwotne miejsce docelowe, a błąd, którego szukasz, jest dokładnie tą częścią, która nigdy nie trafia do pliku.

Dziennik systemowy to kolejne dobre miejsce docelowe. logger zapisuje dane do syslog pod wybranym znacznikiem.

0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-site

Odczytaj dane za pomocą journalctl -t backup-site. Pozwala to zachować wyjście zadania obok wpisów w cron, co ułatwia śledzenie osi czasu. Jeśli potrzebujesz również rejestru tego, kto uruchomił daną komendę na serwerze, jest to zadanie dla osobnego systemu, co opisano w audytowanie poleceń użytkownika na serwerze.

MAILTO="" na początku pliku crontab wyłącza powiadomienia e-mail dla zadań znajdujących się poniżej. Ustawienie MAILTO na poprawny adres pomaga tylko wtedy, gdy działa MTA, więc upewnij się, że poczta opuszcza serwer, zanim na tym polegniesz.

Jedna zasada podczas debugowania: nigdy nie dopisuj > /dev/null 2>&1. Jest to najpopularniejsza linia w każdym crontab, która usuwa jedyne dowody, jakie posiadasz. Przywróć ją później, gdy zadanie będzie działać poprawnie.

Przyczyna 5: skrypt zakłada istnienie środowiska, którego cron nie zapewnia

Gdy polecenie zostanie odnalezione, a jego wyjście przechwycone, pozostaje cała reszta, którą 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 oraz source kończą się błędem składni. Dodaj linię #!/bin/bash do skryptu i wywołuj go w ten sposób lub ustaw SHELL na początku pliku crontab.
  • Katalog roboczy nie jest tym, w którym aktualnie się znajdujesz. Używaj wszędzie ścieżek bezwzględnych lub wykonaj cd do 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 w sesji użytkownika. Wszystko, co formatuje daty, liczby lub sortuje tekst, może generować inne wyniki przy innym LANG. Jeśli późniejszy krok przetwarza to wyjście, ustaw locale bezpośrednio w skrypcie, zamiast polegać na domyślnych wartościach.
  • Brak TTY (terminala). Polecenie oczekujące na potwierdzenie, otwierające edytor lub rysujące pasek postępu może zawisnąć lub zakończyć działanie. Dodaj odpowiednią flagę trybu nieinteraktywnego, jeśli narzędzie ją oferuje.
  • Brak agenta SSH. SSH_AUTH_SOCK nie znajduje się w środowisku crona, więc polecenie ssh lub rsync, 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 szyny sesji użytkownika, więc systemctl --user wywołane z crona kończy się niepowodzeniem, dopóki nie zostanie ustawione XDG_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 zadanie próbujące uzyskać dostęp do ścieżki z nieoczekiwaną etykietą zostanie odrzucone, 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ć, jakie zmienne środowiskowe posiada cron, należy je odczytać. W tym celu należy utworzyć skrypt zrzucający pełną konfigurację, 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.sh

Należy dodać jedną linię do crontab użytkownika, na którego koncie wykonywane jest właściwe zadanie, używając bezwzględnych ścieżek dostępu po obu stronach.

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

Po odczekaniu minuty należy odczytać plik /home/deploy/cron-probe.log i porównać jego zawartość z wynikami tych samych poleceń wykonanych w bieżącej powłoce. Linia PATH, bieżący katalog roboczy oraz ustawienia lokalizacji 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 crona nie mają zastosowania, a ścieżka do pliku dziennika jest zapisywalna dla użytkownika wykonującego zadanie.

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ć niewielki dysk, działając przy tym w sposób niezauważalny.

Czy harmonogram jest zgodny z założeniami?

Wiersz w pliku crontab użytkownika składa się z pięciu pól: minuta, godzina, dzień miesiąca, miesiąc, dzień tygodnia. Dwa z nich wchodzą w interakcję, która często zaskakuje użytkowników.

Gdy zarówno dzień miesiąca, jak i dzień tygodnia są ograniczone, co oznacza, że żadne 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 weryfikację drugiego przeprowadzić 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 godziny popołudniowe w lokalnej strefie. timedatectl wyświetla strefę czasową aktualnie używaną przez serwer. Należy sprawdzić to ustawienie, zamiast zakładać, że jest ono zgodne z ustawieniami komputera lokalnego.

Warto znać jeszcze dwie pułapki związane z harmonogramem. @reboot uruchamia się w momencie startu usługi cron, co nie jest tożsame z momentem uzyskania gotowości sieci. Dlatego zadanie wymagające dostępu do DNS lub zdalnego hosta może zakończyć się niepowodzeniem podczas bootowania, mimo że będzie działać poprawnie przy każdym ręcznym wywołaniu. Ponadto nic nie blokuje ponownego uruchomienia długotrwałego zadania, jeśli poprzednia instancja nadal działa. 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>&1

flock -n kończy działanie natychmiast, jeśli blokada jest już zajęta, dzięki czemu nakładające się uruchomienia zostają przerwane zamiast kumulować się na pierwszej instancji.

Kiedy systemd timer 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 sprawdzić później, możliwość określenia kolejności 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. Przygotowanie części serwisowej wymaga odpowiedzi na pytanie, którego cron nigdy nie zadaje: skąd jednostka wie, że praca faktycznie się rozpoczęła. Dlatego najpierw przeczytaj co oznacza Type= dla jednostek typu simple, forking i notify, ponieważ skrypt, który sam przechodzi w tryb demona przy domyślnym typie, sprawia, że jednostka wygląda na aktywną, mimo że nic nie wykonuje. W tym miejscu należy również zdefiniować zachowanie przy ponownym uruchomieniu, ponieważ polityki restartu systemd decydują o tym, co stanie się po awarii, a cron w ogóle nie oferuje rozwiązań tego problemu.

Pozostaw crona do prostych zadań. Wszystko, co posiada zależności lub wymaga polityki ponawiania prób, przenieś do timera. Oba rozwiązania mogą działać na tym samym serwerze, więc nie jest to migracja, którą trzeba zakończyć za jednym razem.

FAQ

Dlaczego zadanie cron działa ręcznie, a nie uruchamia się z cron?

Ponieważ środowisko powłoki użytkownika 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 często przy użyciu innej powłoki. Należy używać pełnych ścieżek dla każdego polecenia, zdefiniować wymagane zmienne na początku pliku crontab lub wewnątrz skryptu oraz zaplanować zadanie testowe uruchamiane co minutę, które zapisuje wyniki env | sort, pwd oraz id do pliku dziennika. Pozwoli to na odczytanie rzeczywistego środowiska cron zamiast zgadywania.

Jak sprawdzić, czy cron faktycznie uruchomił zadanie?

Należy przejrzeć 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. Należy wyszukać wpis z minutą odpowiadającą harmonogramowi i sprawdzić, czy zawiera on właściwe polecenie. Brak wpisu oznacza, że cron nie posiadał zaplanowanego zadania, więc należy zweryfikować, czy edytowano właściwy plik crontab. Wpis bez rezultatu oznacza, że polecenie zostało uruchomione i zakończyło się błędem, dlatego należy przechwycić jego wyjście za pomocą przekierowania.

Dlaczego date +%Y nie działa poprawnie wewnątrz crontab?

cron traktuje % w polu polecenia w sposób specjalny. Pierwszy niepoprzedzony znakiem ucieczki % kończy polecenie, wszystko co następuje po nim jest przekazywane do tego polecenia jako standardowe wejście, a każdy kolejny % jest zamieniany na znak nowej linii. W rezultacie nazwa pliku sformatowana za pomocą daty nigdy nie dociera do programu w oczekiwanej formie. Należy poprzedzić każdy znak procenta znakiem ucieczki \% lub przenieść polecenie do skryptu i wywoływać skrypt z poziomu cron, ponieważ wewnątrz skryptu znak procenta nie posiada specjalnego znaczenia.

Gdzie trafia wyjście zadania cron?

Do lokalnego systemu pocztowego, adresowanego do właściciela pliku crontab lub użytkownika wskazanego przez MAILTO. Większość obrazów VPS nie posiada zainstalowanego agenta przesyłania poczty, więc wiadomość jest odrzucana, a zadanie wydaje się nie generować żadnego wyjścia. Należy przekierować wyjście do pliku za pomocą >> /path/to/log 2>&1, zachowując tę kolejność, aby standardowy strumień błędów podążał za standardowym wyjściem, lub przesłać je przez logger -t myjob i odczytać za pomocą journalctl -t myjob. Nie należy używać > /dev/null 2>&1 podczas debugowania.

Czy używać cron czy systemd timer?

Cron należy stosować do prostych poleceń uruchamianych o określonej porze, szczególnie gdy istnieje potrzeba przeniesienia zadania na maszynę, która nie korzysta z systemd. Timer należy stosować, gdy wymagane jest zapisywanie wyjścia w dzienniku bez konieczności przekierowań, możliwość odpytania o status zakończenia, uruchomienie po nawiązaniu połączenia sieciowego, losowe opóźnienie startu lub polityka ponawiania prób po wystąpieniu błędu. Oba rozwiązania mogą współistnieć na tym samym serwerze, co pozwala na stopniową migrację zadań w miarę potrzeb.