SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor

Perché il cron job non parte: 5 cause comuni

Scopri le 5 cause più comuni: PATH minimo, percentuale non protetta, crontab errato, output via posta e script che presume una shell di login.

Perché il tuo cron job non viene eseguito

Un cron job che “non viene mai eseguito” quasi sempre è stato eseguito. È partito in un ambiente diverso dalla tua shell, ha avuto esito negativo entro il primo secondo e il messaggio è stato inviato a una destinazione che non stai controllando. Cinque cause spiegano quasi tutti i casi segnalati: il percorso di ricerca, il carattere percentuale, il file crontab errato, l’output inviato via posta e uno script che si aspetta una sessione di login.

cron è un daemon, cioè un servizio in background, che legge i file crontab e avvia i comandi secondo una pianificazione. Non legge il tuo .bashrc, non apre un terminale, non avvia una login shell e non ti informa quando un comando non riesce. Tutte le cause descritte di seguito derivano da questi quattro fatti.

Verificale nell’ordine indicato e inizia dalla domanda alla base di tutte: cron è stato effettivamente attivato? “cron non ha mai avviato il job” e “il job è stato avviato, ma si è interrotto” sono problemi diversi e non hanno nulla in comune. Rispondi prima a questa domanda.

Cron è stato effettivamente eseguito?

Il daemon ha un nome di unità diverso nelle varie famiglie di distribuzioni. Verifica entrambi i nomi, quindi leggi il log.

systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"

Debian e Ubuntu chiamano l'unità cron. Fedora, Rocky e Alma la chiamano crond. Su una macchina è presente un solo nome, quindi è normale che uno dei due comandi segnali un'unità sconosciuta. Non si tratta di un errore.

Leggi le voci scritte dal sistema in uso. Non cercare una riga copiata da una guida, perché la formulazione varia tra le implementazioni di cron e le diverse configurazioni di logging. Devi verificare soltanto due aspetti: se esiste una voce al minuto indicato dalla pianificazione e se quella voce riporta il tuo comando. Una voce che riporta il tuo comando significa che cron ha completato la propria parte e che il problema si trova all'interno del comando. L'assenza totale di voci significa che cron non ha mai ricevuto la tua pianificazione. Questa è la causa 3 riportata di seguito.

Alcune immagini inviano i messaggi di cron tramite rsyslog a un file invece che al journal. Cerca in /var/log un file con un nome che richiami cron o syslog, quindi leggine la parte finale.

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

Se non esistono né l'unità né il log, è possibile che cron non sia installato. Le immagini cloud minimali e i container spesso non lo includono.

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

Causa 1: cron non dispone del tuo PATH

La shell interattiva costruisce PATH a partire da /etc/profile, ~/.profile, ~/.bashrc e da tutti i file caricati da questi ultimi. Nulla di tutto questo viene eseguito per un cron job. cron avvia il comando con un ambiente ridotto, quindi non trova un programma che si trova al di fuori delle directory di sistema standard. Sono candidati tutti i percorsi sotto /usr/local/bin, /opt, un version manager del linguaggio, un ambiente virtuale Python o un workspace Go. Il job non supera la prima riga e la shell scrive un errore del tipo "not found"; il testo esatto dipende dalla shell che lo ha eseguito.

Individua il percorso reale di ogni comando usato dal job.

command -v docker
command -v node
readlink -f "$(command -v node)"

Poi inserisci questi percorsi assoluti nel job oppure imposta PATH una sola volta all'inizio del crontab.

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

Ricava questo elenco dalla tua macchina con echo "$PATH" ed elimina tutto ciò che esiste soltanto all'interno di una sessione interattiva. In questo caso è importante una regola: cron non espande le variabili nelle righe di assegnazione. PATH=$PATH:/usr/local/bin memorizza il testo letterale $PATH:/usr/local/bin, quindi il job si ritrova con un percorso di ricerca che non contiene alcuna directory utilizzabile. Scrivi l'elenco completo.

Un version manager richiede più di un percorso. nvm, pyenv, rbenv e asdf installano una funzione della shell o una directory di shims tramite .bashrc, ma un cron job non legge mai quel file. Richiama il binario della versione tramite il percorso assoluto oppure carica lo script di inizializzazione del manager come prima riga dello script.

Causa 2: il segno di percentuale termina il comando

Nel campo del comando di un crontab, % non è un carattere ordinario. Il primo % non preceduto da un carattere di escape termina il comando. Tutto ciò che segue viene passato al comando come input standard e ogni ulteriore % diventa una nuova riga. Questa è una funzionalità effettiva di cron per fornire input breve a un programma. È anche il motivo per cui un nome file con data è la classica voce errata di un crontab.

Scrivete 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site e tar non riceve mai una data formattata. cron tronca la riga al primo %. La shell riceve quindi una sostituzione di comando non completa e il resto della riga arriva come input standard. Eseguite l'escape di ogni segno di percentuale con una barra rovesciata.

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

Quella riga viene interpretata da due livelli, in quest'ordine. \% è una regola di cron, applicata da cron prima di avviare qualsiasi processo. $(date +\%F) è una sostituzione di comando, applicata in seguito dalla shell avviata da cron. Capire quale livello gestisce ciascun carattere è l'aspetto fondamentale.

L'approccio più sicuro consiste nel tenere la logica completamente fuori dal crontab. Inseritela in uno script, dove il segno di percentuale non ha un significato speciale.

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

La riga del crontab contiene quindi soltanto un percorso e un redirect. Un crontab leggibile a colpo d'occhio è anche un crontab più facile da sottoporre a debug.

Causa 3: quale crontab hai modificato?

Non esiste un solo crontab. Esistono più file, con proprietari e numero di campi diversi, e un job scritto nel file sbagliato non viene rilevato.

  • crontab -e modifica il crontab dell'utente che esegue il comando. sudo crontab -e modifica quello di root. Quando due persone eseguono il debug dello stesso server, spesso finiscono per leggere due file diversi.
  • sudo crontab -l -u deploy elenca il crontab di un altro utente. È il modo per verificare cosa è effettivamente installato per l'account che dovrebbe eseguire il job.
  • /etc/crontab e tutti i file in /etc/cron.d contengono un campo aggiuntivo tra la pianificazione e il comando: l'utente con cui eseguire il job. Se incolli una riga del crontab utente con cinque campi in /etc/cron.d, la prima parola del comando viene interpretata come nome utente.
  • I file in /etc/cron.d devono avere nomi composti da lettere, cifre, underscore e trattini. Un file chiamato backup.sh o site.conf viene ignorato solo a causa del nome. Rinominalo in backup e controlla di nuovo il log.
  • I file in /etc/cron.d devono appartenere a root e non devono essere scrivibili dal gruppo o da altri utenti. ls -l /etc/cron.d mostra entrambe le informazioni contemporaneamente.
  • Gli script inseriti in /etc/cron.daily e nelle directory analoghe seguono la stessa regola per i nomi e devono avere anche il bit di esecuzione. Se manca il bit di esecuzione, il file viene ignorato senza messaggi.
  • /etc/cron.allow e /etc/cron.deny determinano chi può installare un crontab. Se nel server esiste uno dei due file, leggilo prima di presumere che il tuo utente possa installarne uno.

Installa un crontab utente con il comando crontab invece di modificare manualmente il file di spool, perché crontab analizza il file prima di installarlo. Dopo il salvataggio, leggi ciò che il comando stampa. Se il file viene rifiutato, la versione precedente resta attiva e la modifica non viene applicata. Il risultato è identico a quello di cron che ignora il job.

Il proprietario determina anche i permessi. Un job nel crontab di root crea file appartenenti a root, che l'applicazione incaricata di leggerli potrebbe non poter scrivere. Un job nel crontab di un utente normale non può leggere una directory accessibile soltanto a root. Assegna il proprietario in base all'attività: la manutenzione dell'applicazione deve appartenere all'account dell'applicazione. Questo è il motivo per cui conviene leggere sostituire wp-cron di WordPress con un job cron di sistema. La modalità dei file creati dal job deriva dall'umask ereditato. Anche questo valore può essere diverso da quello della shell. Se l'output del job non è leggibile, consulta come umask imposta i permessi dei file.

Causa 4: l'output veniva inviato a una casella che nessuno legge

cron raccoglie tutto ciò che un job scrive sullo standard output e sullo standard error. Se il job scrive anche un solo carattere, cron consegna il testo al sistema di posta locale, indirizzandolo al proprietario del crontab o a ciò che specifica MAILTO. Su un VPS minimale di solito non è installato alcun MTA (mail transfer agent), quindi il messaggio non viene consegnato. L'errore è esistito per un momento e poi non è arrivato da nessuna parte. Questo spiega perché un job non funzionante sembra non produrre errori.

Reindirizza invece l'output a un file che puoi controllare.

0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

>> aggiunge lo standard output al file. 2>&1 indirizza lo standard error alla destinazione attuale dello standard output, quindi deve essere scritto dopo il redirect. Se li scrivi nell'ordine inverso, come in 2>&1 >> file, lo standard error mantiene la destinazione originale e l'errore che stai cercando è proprio la parte che non raggiunge il file.

Il journal è l'altro obiettivo adatto. logger scrive nel syslog usando un tag a tua scelta.

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

Puoi rileggerlo con journalctl -t backup-site. In questo modo l'output del job resta accanto alle voci cron e la sequenza temporale è facile da seguire. Se ti serve anche registrare quale persona ha eseguito ogni comando sul server, si tratta di un sistema distinto; l'auditing dei comandi degli utenti sul server lo descrive.

MAILTO="" all'inizio di un crontab disabilita l'invio della posta per i job riportati sotto. Impostare MAILTO su un indirizzo reale è utile soltanto se esiste un MTA funzionante, quindi verifica che la posta esca dal server prima di fare affidamento su questo meccanismo.

Durante il troubleshooting, segui una regola: non aggiungere mai > /dev/null 2>&1. È la riga più comune in ogni crontab e scarta l'unica prova disponibile. Potrai ripristinarla in seguito, se vuoi, quando il job funzionerà.

Causa 5: lo script presuppone un ambiente che cron non fornisce

Una volta individuato il comando e acquisito il relativo output, resta tutto ciò che la sessione fornisce automaticamente.

  • La shell potrebbe non essere bash. Verifica con ls -l /bin/sh. Su Debian e Ubuntu punta a dash, quindi il test con doppie parentesi, gli array e source generano un errore di sintassi. Inserisci nel script una riga #!/bin/bash e richiama lo script, oppure imposta SHELL all'inizio del crontab.
  • La directory di lavoro non è quella in cui ti trovavi. Usa ovunque percorsi assoluti oppure cd nella directory nella prima riga dello script. Un percorso relativo è il motivo più comune per cui un job «funziona quando lo eseguo manualmente».
  • La locale non è quella della sessione. Qualsiasi comando che formatta una data o un numero oppure ordina del testo può produrre un output diverso con una LANG differente. Se un passaggio successivo analizza quell'output, imposta la locale nello script invece di fare affidamento sul caso.
  • Non è disponibile alcun TTY (terminale). Un comando che richiede una conferma, apre un editor o visualizza una barra di avanzamento può restare bloccato o terminare. Aggiungi il flag non interattivo messo a disposizione dallo strumento.
  • Non è disponibile alcun agente SSH. SSH_AUTH_SOCK non è presente nell'ambiente di cron, quindi un comando ssh o rsync che funzionava perché il tuo agente era stato caricato ora non riesce ad autenticarsi. Assegna al job una chiave propria, di cui l'utente del job sia il proprietario.
  • Non è disponibile alcun bus di sessione utente, quindi systemctl --user eseguito da un job cron fallisce finché non viene impostato XDG_RUNTIME_DIR. Un'unità di sistema è la soluzione migliore.

Su Fedora, Rocky e Alma c'è un'ulteriore possibilità. SELinux confina i job cron, quindi un job che accede a un percorso con un'etichetta imprevista viene negato anche quando i permessi del file sembrano corretti. Verifica i dinieghi con sudo ausearch -m avc -ts recent e leggi Nozioni di base su SELinux per un server prima di disattivare qualsiasi impostazione.

La sonda di un minuto che mostra l'ambiente di cron

Smetti di indovinare cosa contiene l'ambiente di cron e leggilo. Scrivi uno script che scarichi tutte le variabili, pianificalo ogni minuto, attendi, quindi leggi il file.

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

Aggiungi una riga al crontab dell'utente con cui viene eseguito il job reale, usando percorsi assoluti su entrambi i lati.

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

Attendi un minuto, quindi leggi /home/deploy/cron-probe.log e confrontalo con gli stessi comandi eseguiti nella tua shell. La riga PATH, la directory di lavoro e la locale spiegano spesso da sole il problema. Nota due dettagli della configurazione: i caratteri percentuale si trovano nello script, dove la regola di cron non si applica, e il percorso del log è scrivibile dall'utente del job.

Elimina la riga dal crontab non appena hai trovato la risposta. Un job eseguito ogni minuto che aggiunge contenuto a un file riempirà un disco di piccole dimensioni, e lo farà senza produrre avvisi.

La pianificazione è quella prevista?

La riga di un crontab utente inizia con cinque campi: minuto, ora, giorno del mese, mese, giorno della settimana. Due di questi interagiscono in un modo che spesso sorprende.

Quando il giorno del mese e il giorno della settimana sono entrambi limitati, cioè nessuno dei due è *, cron esegue il job quando corrisponde uno dei due campi. 0 0 13 * 5 non significa «venerdì 13». Il job viene eseguito a mezzanotte il giorno 13 di ogni mese e a mezzanotte ogni venerdì. Per ottenere un solo giorno specifico, lascia uno dei due campi impostato su * e verifica l’altro all’interno dello script.

cron usa il fuso orario del sistema. Molte immagini VPS vengono distribuite con UTC (Coordinated Universal Time) come fuso orario, quindi un job pianificato per le 03:00 viene eseguito alle 03:00 UTC, che potrebbe corrispondere al pomeriggio inoltrato nel tuo fuso orario. timedatectl mostra il fuso orario effettivamente utilizzato dal server. Verifica quello configurato sul server invece di presumere che corrisponda a quello del laptop.

È importante conoscere altri due problemi relativi alla pianificazione. @reboot viene eseguito quando viene avviato cron, che non coincide necessariamente con il momento in cui la rete è pronta. Di conseguenza, un job che richiede DNS o un host remoto può non riuscire durante l’avvio e riuscire invece a ogni esecuzione manuale successiva. Inoltre, nulla impedisce a un job lento di avviarsi di nuovo mentre la copia precedente è ancora in esecuzione. Usa un lock per impedirlo.

*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

flock -n termina immediatamente quando il lock è già acquisito, quindi l’esecuzione sovrapposta si interrompe invece di accumularsi sopra la prima.

Quando systemd timer è lo strumento più adatto

cron è efficace per una sola operazione: eseguire un comando a un'ora specifica. Per tutto il resto è limitato. Un timer rende disponibili i log nel journal senza redirect, consente di verificare in seguito il codice di uscita, permette di definire l'ordine rispetto a network-online.target e supporta un ritardo casuale, così un centinaio di server non avvia il processo nello stesso secondo. Quando il job richiede una di queste funzioni, un servizio e un timer systemd su un VPS richiedono meno lavoro rispetto alla gestione di una riga in crontab. Anche il comportamento in caso di retry va definito qui, perché le policy di riavvio di systemd determinano cosa accade dopo un errore, mentre cron non offre alcun meccanismo per gestire questo caso.

Usa cron per i job semplici. Sposta in un timer tutto ciò che ha dipendenze o una policy di retry. Entrambi possono essere eseguiti sullo stesso server, quindi non devi completare questa migrazione in un'unica sessione.

FAQ

Perché il mio cron job funziona manualmente ma fallisce quando viene eseguito da cron?

Perché l'ambiente della shell e quello di cron sono diversi. La shell di login legge /etc/profile e ~/.bashrc, che impostano PATH, la locale e le variabili dell'agent. cron avvia il comando senza nessuno di questi elementi, da una directory di lavoro diversa e talvolta con una shell diversa. Usate percorsi assoluti per ogni comando, impostate ciò che serve all'inizio del crontab o nello script e pianificate un cron job di prova, con intervallo di un minuto, che esegua env | sort, pwd e id scrivendo l'output in un file di log. In questo modo potete leggere l'ambiente effettivo di cron invece di ricostruirlo per ipotesi.

Come posso verificare se cron ha eseguito realmente il mio cron job?

Leggete il log del demone. Usate journalctl -u cron su Debian e Ubuntu oppure journalctl -u crond su Fedora, Rocky e Alma. In alcune immagini, invece, rsyslog inoltra questi messaggi in un file sotto /var/log. Cercate una voce nel minuto indicato dalla pianificazione e verificate che riporti il vostro comando. Se non esiste alcuna voce, cron non ha acquisito la pianificazione: controllate quindi di avere modificato il crontab corretto. Una voce senza risultato indica che il comando è stato avviato ma si è interrotto. Acquisite l'output con un redirect.

Perché date +%Y non funziona in un crontab?

Nel campo del comando, cron tratta % come un carattere speciale. Il primo % non preceduto da escape termina il comando. Tutto ciò che segue viene passato al comando come input standard e ogni ulteriore % viene convertito in una nuova riga. Di conseguenza, un nome di file con data formattata non arriva al programma per cui lo avete scritto. Eseguite l'escape di ogni percentuale come \% oppure spostate il comando in uno script e richiamate lo script da cron. All'interno di uno script, infatti, il segno di percentuale non ha un significato speciale.

Dove finisce l'output del mio cron job?

Viene inviato al sistema di posta locale, all'owner del crontab oppure al destinatario indicato da MAILTO. La maggior parte delle immagini VPS non include un mail transfer agent, quindi il messaggio viene scartato e il cron job sembra non produrre output. Reindirizzate l'output in un file con >> /path/to/log 2>&1, mantenendo questo ordine in modo che lo standard error segua lo standard output. In alternativa, passatelo attraverso logger -t myjob e leggetelo con journalctl -t myjob. Non usate > /dev/null 2>&1 mentre state ancora eseguendo il troubleshooting.

Devo usare cron o un timer di systemd?

Usate cron per un comando semplice da eseguire a un'ora prestabilita, soprattutto se potreste doverlo spostare su una macchina che non esegue systemd. Usate un timer quando volete l'output nel journal senza redirect, un exit status consultabile, l'ordinamento dopo la disponibilità della rete, un ritardo di avvio casuale oppure un criterio di nuovo tentativo dopo un errore. Entrambe le soluzioni possono essere eseguite sullo stesso server. Potete quindi spostare i cron job uno alla volta, quando necessario.