SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-09-04

Perché il job cron non viene eseguito

Cinque cause spiegano quasi tutti i job cron ignorati: PATH minimo, percentuale non escapata, crontab errato, output perso via posta e script legato alla shell.

Perché il tuo job cron non viene eseguito

Un job cron che «non viene mai eseguito» quasi sempre è stato avviato. È stato eseguito in un ambiente diverso dalla tua shell, è terminato entro il primo secondo e il messaggio è stato inviato in un punto 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 presuppone una sessione di login.

cron è un daemon (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 shell di login e non ti segnala quando un comando non riesce. Ogni causa descritta di seguito deriva da questi quattro fatti.

Analizzale nell’ordine indicato e inizia dalla domanda comune a tutte: cron ha attivato il job? «cron non ha mai avviato il job» e «il job è stato avviato, ma è terminato» sono problemi diversi e non hanno nulla in comune. Devi quindi rispondere prima a questa domanda.

Cron è stato eseguito?

Il daemon ha un nome diverso a seconda della famiglia 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 determinata macchina esiste uno solo di questi nomi, quindi è normale che uno dei due comandi restituisca 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 diverse implementazioni di cron e tra le configurazioni di logging. Devi verificare soltanto due aspetti: se esiste una voce al minuto indicato dalla pianificazione e se quella voce contiene il nome del comando. Una voce che contiene il nome del comando indica che cron ha svolto la propria parte e che il problema è interno al comando. L'assenza completa di voci indica che cron non ha mai caricato la pianificazione. Questa è la causa 3 descritta di seguito.

Alcune immagini inviano i messaggi di cron tramite rsyslog a un file invece di usare il journal. Cerca in /var/log un file con un nome che contiene 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 ne sono privi.

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 inclusi da questi ultimi. Nulla di tutto questo viene eseguito per un job cron. 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 possibili candidati i programmi installati in /usr/local/bin, /opt, da un gestore delle versioni del linguaggio, da un ambiente virtuale Python o da un workspace Go. Il job non supera la prima riga e la shell scrive un errore del tipo "not found"; la formulazione esatta dipende dalla shell che lo ha eseguito.

Individua il percorso effettivo 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" e rimuovi tutto ciò che esiste soltanto in una sessione interattiva. In questo caso è importante una regola: cron non espande le variabili nelle righe di assegnazione. PATH=$PATH:/usr/local/bin conserva 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 gestore delle versioni richiede più del semplice percorso. nvm, pyenv, rbenv e asdf installano una funzione della shell o una directory di shims a partire dal tuo .bashrc, ma un job cron non legge mai quel file. Richiama il binario della versione desiderata tramite il percorso assoluto oppure esegui lo script di inizializzazione del gestore come prima riga del tuo script.

Causa 2: il carattere percentuale interrompe 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 standard input e ogni ulteriore % diventa una nuova riga. Questa è una funzione reale di cron per fornire input brevi a un programma. È anche il motivo per cui un nome di file con la data è la tipica voce di crontab non funzionante.

Scrivete 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site e tar non riceverà mai una data formattata. cron tronca la riga al primo %. La shell riceve quindi una sostituzione di comando incompleta e il resto della riga arriva come standard input. Eseguite l'escape di ogni carattere 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 comando. $(date +\%F) è la sostituzione di comando, applicata in seguito dalla shell avviata da cron. Il punto fondamentale è sapere quale livello interpreta ciascun carattere.

L'approccio più sicuro consiste nel tenere la logica fuori dal crontab. Inseritela in uno script, dove il carattere 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 conterrà quindi soltanto un percorso e un redirect. Un crontab leggibile a colpo d'occhio è anche un crontab che si può eseguire facilmente il debug.

Causa 3: quale crontab hai modificato?

Non esiste un solo crontab. Esistono diversi file, con proprietari e numero di campi differenti; 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. Due persone che eseguono il troubleshooting dello stesso server spesso finiscono per leggere due file diversi.
  • sudo crontab -l -u deploy elenca il crontab di un altro utente. È così che puoi verificare cosa è effettivamente installato per l'account che dovrebbe eseguire il job.
  • /etc/crontab e ogni 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 di 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, caratteri di sottolineatura 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 entrambi i dati 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, lo script viene ignorato senza messaggi.
  • /etc/cron.allow e /etc/cron.deny stabiliscono chi può installare un crontab. Se uno dei due file esiste sul server, leggilo prima di presumere che il tuo utente abbia questa autorizzazione.

Installa un crontab utente con il comando crontab invece di modificare manualmente il file spool, perché crontab analizza il file prima di installarlo. Dopo il salvataggio, leggi ciò che il comando stampa. Se rifiuta il file, 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 riuscire a modificare. Un job nel crontab di un utente normale non può leggere una directory accessibile solo a root. Associa il proprietario all'attività: la manutenzione di un'applicazione deve essere eseguita dall'account dell'applicazione. Questo è il motivo alla base di sostituire wp-cron di WordPress con un job cron di sistema. La modalità dei file creati dal job dipende dall'umask ereditato. Anche questo valore può essere diverso da quello della shell. Se l'output di un job non è leggibile, conviene quindi consultare come umask imposta i permessi dei file.

Causa 4: l'output viene 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 qualcosa, cron passa quel testo al sistema di posta locale, indirizzandolo al proprietario del crontab o all'indirizzo indicato da MAILTO. Su un VPS minimale di solito non è installato alcun MTA (mail transfer agent), quindi il messaggio non viene recapitato. L'errore esiste per un momento e poi va perso. Questo spiega perché un job non funzionante sembra non produrre alcun errore.

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 verso la destinazione a cui punta in quel momento lo standard output, quindi deve comparire dopo il reindirizzamento. 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 è un'altra destinazione adatta. logger scrive nel syslog usando il tag che scegli.

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 prodotto dal job resta accanto alle voci di cron e la sequenza temporale è facile da seguire. Se ti serve anche registrare quale persona ha eseguito ciascun comando sul server, si tratta di un sistema distinto; la guida controllare i comandi eseguiti dagli utenti sul server lo descrive.

MAILTO="" all'inizio di un crontab disabilita l'invio della posta per i job che seguono. Impostare MAILTO su un indirizzo reale è utile solo se è disponibile un MTA funzionante, quindi verifica che la posta venga effettivamente inviata dal server prima di farci affidamento.

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. Verificare 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. Inserire nel script una riga #!/bin/bash e richiamare lo script, oppure impostare SHELL all'inizio del crontab.
  • La directory di lavoro non è quella in cui ci si trovava. Usare ovunque percorsi assoluti oppure cd alla 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 diversa. Se un passaggio successivo analizza quell'output, impostare la locale nello script invece di fare affidamento sul valore predefinito.
  • Non è disponibile un TTY (terminale). Un comando che richiede una conferma, apre un editor o visualizza una barra di avanzamento può bloccarsi o terminare. Aggiungere il flag non interattivo offerto dallo strumento.
  • Non è disponibile un agente SSH. SSH_AUTH_SOCK non è presente nell'ambiente di cron, quindi un comando ssh o rsync che funzionava perché l'agente era stato caricato ora non riesce ad autenticarsi. Fornire al job una chiave dedicata, di proprietà dell'utente che esegue il job.
  • Non è disponibile un bus di sessione dell'utente, quindi systemctl --user eseguito da un job cron non funziona finché non viene impostato XDG_RUNTIME_DIR. Un'unità di sistema è la soluzione migliore.

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

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

Non fare supposizioni sul contenuto dell'ambiente di cron: leggilo. Scrivi uno script che scarichi tutto, 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 che esegue il job.

Elimina la riga dal crontab non appena hai individuato la causa. Un job eseguito ogni minuto che aggiunge dati a un file può riempire un disco di piccole dimensioni, e lo farà senza produrre avvisi.

La pianificazione è quella prevista?

Una riga nel crontab dell'utente inizia con cinque campi: minuto, ora, giorno del mese, mese, giorno della settimana. Due di questi interagiscono in un modo che spesso causa confusione.

Quando il giorno del mese e il giorno della settimana sono entrambi limitati, cioè nessuno dei due è *, cron esegue il job quando corrisponde uno qualunque dei due campi. 0 0 13 * 5 non significa "venerdì 13". Esegue il job a mezzanotte del giorno 13 di ogni mese e a mezzanotte di ogni venerdì. Per indicare 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 il fuso orario impostato su UTC (tempo coordinato universale), 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 usato dal server. Controlla il valore presente sul server invece di presumere che corrisponda a quello del laptop.

È utile conoscere altri due problemi comuni nelle pianificazioni. @reboot viene eseguito quando si avvia 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ò fallire all'avvio e riuscire in tutte le successive esecuzioni manuali. Inoltre, nulla impedisce a un job lento di avviarsi di nuovo mentre la copia precedente è ancora in esecuzione. Usa un lock.

*/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 alla prima.

Quando systemd timer è lo strumento più adatto

cron è efficace per una sola cosa: eseguire questo comando a quest’ora. Per tutto il resto è limitato. Un timer mette a disposizione il journal senza redirect, uno stato di uscita che puoi verificare in seguito, l’ordinamento rispetto a network-online.target e un ritardo casuale, così un centinaio di server non avviano tutti il job nello stesso secondo. Quando il job richiede una di queste funzioni, un servizio e un timer systemd su un VPS richiedono meno lavoro che dover gestire una riga di crontab. La parte relativa al servizio introduce una domanda che cron non pone mai: come fa l’unità a sapere che il lavoro è effettivamente iniziato? Leggi quindi prima il significato di Type= per le unità simple, forking e notify, perché uno script che esegue autonomamente il daemonizing con il tipo predefinito lascia l’unità nello stato active, senza alcun processo sottostante. Anche il comportamento in caso di retry va definito qui, perché le policy di riavvio di systemd stabiliscono cosa accade dopo un errore, mentre cron non offre alcuna risposta a questa domanda.

Mantieni 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 tramite 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 localizzazione e le variabili dell’agent. cron avvia il comando senza nessuna di queste impostazioni, da una directory di lavoro diversa e talvolta con una shell diversa. Usa percorsi assoluti per ogni comando, imposta ciò che serve all’inizio del crontab o nello script e pianifica un job di prova con intervallo di un minuto che esegua env | sort, pwd e id in un file di log. In questo modo puoi leggere l’ambiente reale di cron invece di procedere per tentativi.

Come verifico se cron ha effettivamente eseguito il mio job?

Leggi il log del demone. Usa journalctl -u cron su Debian e Ubuntu, oppure journalctl -u crond su Fedora, Rocky e Alma; alcune immagini inoltrano invece i messaggi tramite rsyslog a un file sotto /var/log. Cerca una voce al minuto indicato dalla pianificazione e verifica che riporti il comando. Se non c’è alcuna voce, cron non ha mai acquisito la pianificazione: verifica quindi di aver modificato il crontab corretto. Una voce senza risultato indica che il comando è stato avviato ma si è interrotto; acquisisci il suo output con un redirect.

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

cron tratta % come un carattere speciale nel campo del comando. Il primo % non preceduto da escape termina il comando; tutto ciò che segue viene passato a quel 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 hai scritto. Esegui l'escape di ogni percentuale come \% oppure sposta il comando in uno script e richiama lo script da cron, perché all'interno di uno script il segno di percentuale non ha un significato speciale.

Dove finisce l'output del mio cron job?

Nel sistema di posta locale, indirizzato al proprietario del crontab o a ciò che specifica MAILTO. La maggior parte delle immagini VPS non include un mail transfer agent, quindi il messaggio viene scartato e il job sembra non produrre output. Reindirizza l'output a un file con >> /path/to/log 2>&1, mantenendo quest'ordine affinché lo standard error segua lo standard output, oppure inoltralo tramite logger -t myjob e rileggilo con journalctl -t myjob. Non usare > /dev/null 2>&1 finché il troubleshooting è ancora in corso.

Devo usare cron o un timer di systemd?

Usa cron per un comando semplice eseguito a un orario fisso, soprattutto se potresti doverlo spostare su una macchina che non esegue systemd. Usa un timer quando vuoi l'output nel journal senza redirect, un codice di uscita interrogabile, l'esecuzione dopo la disponibilità della rete, un ritardo di avvio casuale o una policy di ritentativo dopo un errore. Entrambi possono essere eseguiti sullo stesso server, quindi puoi trasferire i job uno alla volta quando ne vale la pena.