Cron vs systemd timer
⏱️ Cron vs Systemd Timer — Guida Pratica alla Pianificazione su Linux
Cron è affidabile e ovunque presente, ma i timer systemd sono l'alternativa moderna: logging integrato, recupero delle esecuzioni mancate, riattivazione dalla sospensione, gestione delle dipendenze e molto altro. Questa guida copre entrambi con esempi pratici pronti all'uso.
🗂️ Indice
- #1. Cron — Il classico
- #2. Systemd Timer — Il moderno
- #3. Confronto diretto
- #4. Esempi pratici — Cron
- #5. Esempi pratici — Systemd Timer
- #6. Casi d'uso reali
- #7. Gestione e debug
- #8. Quando usare uno o l'altro
1. Cron — Il classico
Cron è nato negli anni '70 nei sistemi Unix ed è ancora oggi largamente installato su qualsiasi distribuzione Linux. La sua forza è la semplicità e l'ubiquità: funziona ovunque, senza dipendenze, e tutti gli amministratori lo conoscono.
1.1 Come funziona cron
Cron legge i file crontab (cron table) e li esegue secondo la pianificazione definita. Ogni utente può avere il proprio crontab, oltre ai crontab di sistema.
# Modifica il crontab dell'utente corrente
crontab -e
# Visualizza il crontab corrente
crontab -l
# Rimuovi il crontab corrente
crontab -r
# Visualizza il crontab di un altro utente (root)
crontab -l -u username
# Modifica il crontab di un altro utente (root)
crontab -e -u username
1.2 Sintassi del crontab
┌───────────── minuto (0–59)
│ ┌─────────── ora (0–23)
│ │ ┌───────── giorno mese (1–31)
│ │ │ ┌─────── mese (1–12 o jan–dec)
│ │ │ │ ┌───── giorno sett. (0–7, 0 e 7 = domenica; o sun–sat)
│ │ │ │ │
* * * * * comando
Caratteri speciali:
| Carattere | Significato | Esempio |
|---|---|---|
* |
qualsiasi valore | * = ogni minuto/ora/giorno |
, |
elenco di valori | 1,15,30 = al minuto 1, 15 e 30 |
- |
intervallo | 9-17 = dalle 9 alle 17 |
/ |
step/passo | */5 = ogni 5 unità |
@reboot |
all'avvio | @reboot = una volta al boot |
@hourly |
ogni ora | equivale a 0 * * * * |
@daily |
ogni giorno | equivale a 0 0 * * * |
@weekly |
ogni settimana | equivale a 0 0 * * 0 |
@monthly |
ogni mese | equivale a 0 0 1 * * |
@yearly |
ogni anno | equivale a 0 0 1 1 * |
1.3 File crontab di sistema
Oltre al crontab utente, esistono file e directory per i crontab di sistema:
/etc/crontab # crontab principale di sistema (ha anche campo utente)
/etc/cron.d/ # directory per crontab aggiuntivi (pacchetti, app)
/etc/cron.hourly/ # script eseguiti ogni ora
/etc/cron.daily/ # script eseguiti ogni giorno
/etc/cron.weekly/ # script eseguiti ogni settimana
/etc/cron.monthly/ # script eseguiti ogni mese
/etc/crontab vs crontab utente
Il file /etc/crontab e quelli in /etc/cron.d/ hanno un campo aggiuntivo per l'utente con cui eseguire il comando:
* * * * * root /percorso/comando
Il crontab utente (da crontab -e) non ha questo campo.
2. Systemd Timer — Il moderno
I timer systemd sono unità speciali (.timer) che attivano un'altra unità, quasi sempre un servizio (.service). Fanno parte dell'ecosistema systemd e sono presenti su tutte le distribuzioni moderne.
2.1 Architettura: coppia .timer + .service
Ogni timer è composto da due file:
/etc/systemd/system/
├── mio-script.service ← cosa fare
└── mio-script.timer ← quando farlo
Se il file .timer si chiama backup.timer, per default attiva backup.service. Il collegamento è automatico per nome. Si può cambiare con la direttiva Unit= nel .timer.
2.2 Struttura di un file .service
[Unit]
Description=Descrizione del servizio
# Documentazione opzionale
Documentation=https://esempio.com/docs
[Service]
Type=oneshot
User=username
Group=groupname
WorkingDirectory=/opt/mia-app
# Variabili d'ambiente
Environment="VARIABILE=valore"
EnvironmentFile=/etc/mia-app/env
# Il comando da eseguire (percorso ASSOLUTO sempre)
ExecStart=/usr/bin/python3 /opt/scripts/mio-script.py
# Condizione opzionale: esegue solo se il file esiste
# ExecCondition=/usr/bin/test -f /tmp/abilitato
# Output nei log systemd
StandardOutput=journal
StandardError=journal
Type=oneshot
Per i job pianificati si usa quasi sempre Type=oneshot: il servizio esegue il comando, poi termina. Systemd lo considera "completato con successo" solo se il comando esce con codice 0.
2.3 Struttura di un file .timer
[Unit]
Description=Descrizione del timer
[Timer]
# --- Pianificazione basata sul calendario (come cron) ---
OnCalendar=*-*-* 02:00:00 # ogni giorno alle 02:00
# --- Pianificazione relativa ---
# OnBootSec=15min # 15 minuti dopo il boot
# OnUnitActiveSec=1h # 1 ora dopo l'ultima esecuzione
# --- Opzioni utili ---
Persistent=true # recupera esecuzioni perse (sistema era spento)
RandomizedDelaySec=300 # ritardo casuale fino a 5 minuti
# WakeSystem=true # riattiva il sistema dalla sospensione
[Install]
WantedBy=timers.target
2.4 Direttive OnCalendar — sintassi
DayOfWeek Year-Month-Day Hour:Minute:Second
| Espressione | Significato |
|---|---|
daily |
ogni giorno a mezzanotte |
weekly |
ogni lunedì a mezzanotte |
monthly |
il 1° di ogni mese a mezzanotte |
hourly |
ogni ora all'inizio dell'ora |
minutely |
ogni minuto |
*-*-* 02:00:00 |
ogni giorno alle 02:00 |
Mon *-*-* 09:00:00 |
ogni lunedì alle 09:00 |
Mon..Fri *-*-* 08:30:00 |
da lunedì a venerdì alle 08:30 |
*-*-1 00:00:00 |
il primo di ogni mese |
*-*-* *:00/15:00 |
ogni 15 minuti |
Sat,Sun *-*-* 10:00:00 |
sabato e domenica alle 10:00 |
*-*-* 00/6:00:00 |
ogni 6 ore |
# Verifica e normalizza un'espressione calendario
systemd-analyze calendar "Mon..Fri *-*-* 08:30:00"
systemd-analyze calendar "daily"
systemd-analyze calendar "*:00/15"
2.5 Direttive temporali relative
[Timer]
OnActiveSec=10s # 10 secondi dopo l'attivazione del timer
OnBootSec=5min # 5 minuti dopo il boot
OnStartupSec=1h # 1 ora dopo l'avvio di systemd
OnUnitActiveSec=24h # 24 ore dopo l'ultima esecuzione del servizio
OnUnitInactiveSec=2h # 2 ore dopo l'ultima terminazione del servizio
3. Confronto diretto
| Caratteristica | Cron | Systemd Timer |
|---|---|---|
| Disponibilità | Ovunque, sempre | Sistemi con systemd (tutte le distro moderne) |
| Logging | Nessuno nativo (email o /dev/null) | journalctl integrato |
| Ambiente di esecuzione | Minimale, $PATH ridotto | Definito esplicitamente nel .service |
| Esecuzioni mancate | Perse per sempre | Recuperate con Persistent=true |
| Riattivazione dalla sospensione | No | Sì, con WakeSystem=true |
| Dipendenze | Non gestite | Gestite tramite systemd |
| Sintassi | Compatta ma criptica | Verbosa ma esplicita e leggibile |
| Panoramica attività | crontab -l per ogni utente |
systemctl list-timers globale |
| Ritardo casuale | Non nativo | RandomizedDelaySec= |
| Condizioni pre-esecuzione | Non native | ExecCondition=, ConditionPathExists= |
| Utente di esecuzione | Nel crontab di sistema | User= nel .service |
| Limiti di risorse | Non disponibili | CPU, memoria, I/O via cgroup |
| Override senza modifica | Non supportato | systemctl edit per drop-in |
| Visibilità globale | Sparsa tra utenti e directory | Centralizzata in systemd |
- Hai bisogno di qualcosa di semplice e rapido su un sistema con utenti diversi → cron
- Stai costruendo qualcosa di serio, in produzione, su un server moderno → systemd timer
4. Esempi pratici — Cron
4.1 Esempi di espressioni crontab
# Apri il crontab con:
crontab -e
# ─────────────────────────────────────────────────────────────────
# CRONTAB — esempi pratici
# ─────────────────────────────────────────────────────────────────
# Ogni giorno alle 2:30
30 2 * * * /opt/scripts/backup.sh
# Ogni lunedì alle 9:00
0 9 * * 1 /opt/scripts/report-settimanale.sh
# Ogni ora (al minuto 0)
0 * * * * /opt/scripts/sync.sh
# Ogni 15 minuti
*/15 * * * * /opt/scripts/check-status.sh
# Dal lunedì al venerdì alle 8:30
30 8 * * 1-5 /opt/scripts/avvio-lavoro.sh
# Il primo di ogni mese a mezzanotte
0 0 1 * * /opt/scripts/report-mensile.sh
# Ogni giorno alle 6:00 e alle 18:00
0 6,18 * * * /opt/scripts/doppio-check.sh
# Il sabato e la domenica alle 10:00
0 10 * * 6,0 /opt/scripts/backup-weekend.sh
# All'avvio del sistema
@reboot /opt/scripts/startup.sh
# Ogni giorno alle 3:00 — scarta output su /dev/null
0 3 * * * /opt/scripts/cleanup.sh > /dev/null 2>&1
# Ogni giorno alle 3:00 — salva output su file di log
0 3 * * * /opt/scripts/cleanup.sh >> /var/log/cleanup.log 2>&1
4.2 Crontab di sistema con utente
# Modifica il crontab di sistema
sudo nano /etc/crontab
# oppure crea un file in /etc/cron.d/
sudo nano /etc/cron.d/mia-applicazione
# /etc/cron.d/mia-applicazione
# Formato: minuto ora giorno mese settimana utente comando
# Backup notturno eseguito come www-data
0 2 * * * www-data /opt/webapp/scripts/backup.sh >> /var/log/webapp-backup.log 2>&1
# Pulizia cache eseguita come root
*/30 * * * * root /usr/local/bin/clear-cache.sh
# Report mensile eseguito come deploy
0 8 1 * * deploy /opt/deploy/report.py >> /var/log/deploy-report.log 2>&1
4.3 Script cron robusti
Gli script eseguiti da cron hanno un $PATH molto ridotto. Usa sempre percorsi assoluti sia nel crontab che negli script.
#!/bin/bash
# /opt/scripts/backup.sh
# Script sicuro da usare con cron
# Imposta PATH esplicitamente
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Variabili con percorsi assoluti
LOG_FILE="/var/log/backup.log"
BACKUP_DIR="/backup"
SOURCE_DIR="/var/www/html"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
# Funzione di log
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
log "Avvio backup..."
# Controlla che la destinazione esista
if [ ! -d "$BACKUP_DIR" ]; then
log "ERRORE: directory backup non trovata: $BACKUP_DIR"
exit 1
fi
# Esegui backup
/usr/bin/tar -czf "$BACKUP_DIR/backup_$TIMESTAMP.tar.gz" "$SOURCE_DIR" 2>> "$LOG_FILE"
if [ $? -eq 0 ]; then
log "Backup completato: backup_$TIMESTAMP.tar.gz"
else
log "ERRORE: backup fallito"
exit 1
fi
# Rimuovi backup più vecchi di 7 giorni
/usr/bin/find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
log "Pulizia backup vecchi completata"
4.4 Evitare esecuzioni sovrapposte con flock
# Nel crontab: usa flock per evitare esecuzioni parallele
*/5 * * * * /usr/bin/flock -n /tmp/mio-script.lock /opt/scripts/mio-script.sh
#!/bin/bash
# Alternativa: gestione lock dentro lo script
LOCK_FILE="/tmp/backup.lock"
if [ -e "$LOCK_FILE" ]; then
echo "Backup già in esecuzione, skip." >&2
exit 1
fi
trap "rm -f $LOCK_FILE" EXIT
touch "$LOCK_FILE"
# ... script principale ...
5. Esempi pratici — Systemd Timer
5.1 Struttura delle directory
# Timer di sistema (root)
/etc/systemd/system/
# Timer utente (senza root, solo per l'utente corrente)
~/.config/systemd/user/
5.2 Esempio 1 — Backup notturno giornaliero
# Crea il file servizio
sudo nano /etc/systemd/system/backup-notturno.service
# /etc/systemd/system/backup-notturno.service
[Unit]
Description=Backup notturno dei dati
Documentation=https://wiki.interna/backup
After=network.target
[Service]
Type=oneshot
User=backup
Group=backup
# Variabili d'ambiente
Environment="BACKUP_DEST=/mnt/nas/backup"
Environment="SOURCE=/var/www"
ExecStart=/usr/bin/rsync -av --delete ${SOURCE} ${BACKUP_DEST}
# In caso di errore, riprova dopo 5 minuti (max 3 tentativi)
Restart=on-failure
RestartSec=5min
StartLimitIntervalSec=30min
StartLimitBurst=3
StandardOutput=journal
StandardError=journal
# Crea il file timer
sudo nano /etc/systemd/system/backup-notturno.timer
# /etc/systemd/system/backup-notturno.timer
[Unit]
Description=Avvia il backup notturno ogni giorno alle 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
# Se il sistema era spento alle 02:30, esegui alla prossima accensione
Persistent=true
# Ritardo casuale fino a 10 minuti (utile in ambienti distribuiti)
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
# Abilita e avvia il timer
sudo systemctl daemon-reload
sudo systemctl enable --now backup-notturno.timer
# Verifica
systemctl status backup-notturno.timer
systemctl list-timers | grep backup
5.3 Esempio 2 — Pulizia cache ogni 15 minuti
# /etc/systemd/system/pulizia-cache.service
[Unit]
Description=Pulizia della cache applicazione
[Service]
Type=oneshot
User=www-data
ExecStart=/usr/bin/find /var/cache/webapp -name "*.tmp" -mtime +1 -delete
ExecStart=/usr/bin/find /tmp/webapp -name "sess_*" -mtime +0 -delete
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/pulizia-cache.timer
[Unit]
Description=Pulizia cache ogni 15 minuti
[Timer]
OnCalendar=*:00/15
Persistent=false
[Install]
WantedBy=timers.target
5.4 Esempio 3 — Script dopo il boot (con ritardo)
# /etc/systemd/system/post-boot-setup.service
[Unit]
Description=Configurazione post-avvio
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/opt/scripts/post-boot.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/post-boot-setup.timer
[Unit]
Description=Esegui setup 2 minuti dopo il boot
[Timer]
OnBootSec=2min
[Install]
WantedBy=timers.target
5.5 Esempio 4 — Task ricorrente relativo (ogni 6 ore)
# /etc/systemd/system/sync-dati.service
[Unit]
Description=Sincronizzazione dati con il server remoto
[Service]
Type=oneshot
User=sync-user
ExecStart=/usr/bin/rsync -az --timeout=60 /dati/locali/ utente@server:/dati/remoti/
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/sync-dati.timer
[Unit]
Description=Sync dati ogni 6 ore
[Timer]
# Prima esecuzione: 10 minuti dopo il boot
OnBootSec=10min
# Poi ogni 6 ore dopo l'ultima attivazione
OnUnitActiveSec=6h
[Install]
WantedBy=timers.target
5.6 Esempio 5 — Aggiornamenti automatici di sistema
# /etc/systemd/system/aggiornamento-sistema.service
[Unit]
Description=Aggiornamento automatico pacchetti
After=network-online.target
Wants=network-online.target
# Non avviare se è in corso un altro aggiornamento
ConditionPathExistsGlob=!/var/lib/dpkg/lock*
[Service]
Type=oneshot
User=root
# Verifica connettività prima di aggiornare
ExecCondition=/usr/bin/ping -c 1 -W 5 8.8.8.8
ExecStart=/usr/bin/apt update
ExecStart=/usr/bin/apt upgrade -y --no-install-recommends
ExecStart=/usr/bin/apt autoremove -y
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/aggiornamento-sistema.timer
[Unit]
Description=Aggiornamento settimanale del sistema
[Timer]
# Ogni domenica alle 04:00
OnCalendar=Sun *-*-* 04:00:00
Persistent=true
# Ritardo casuale fino a 30 minuti (utile su fleet di server)
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
5.7 Esempio 6 — Report mensile
# /etc/systemd/system/report-mensile.service
[Unit]
Description=Generazione report mensile utilizzo risorse
[Service]
Type=oneshot
User=monitor
Group=monitor
WorkingDirectory=/opt/monitoring
Environment="REPORT_DIR=/var/reports"
Environment="MAIL_TO=admin@esempio.com"
ExecStart=/usr/bin/python3 /opt/monitoring/genera-report.py
ExecStart=/usr/bin/python3 /opt/monitoring/invia-report.py
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/report-mensile.timer
[Unit]
Description=Report mensile il giorno 1 di ogni mese alle 08:00
[Timer]
OnCalendar=*-*-1 08:00:00
Persistent=true
[Install]
WantedBy=timers.target
5.8 Esempio 7 — Timer utente (senza root)
I timer utente vengono eseguiti con i privilegi dell'utente, senza sudo.
# Crea la directory se non esiste
mkdir -p ~/.config/systemd/user/
# ~/.config/systemd/user/sync-appunti.service
[Unit]
Description=Sincronizzazione appunti personali
[Service]
Type=oneshot
ExecStart=/usr/bin/rsync -az ~/Appunti/ %h/Nextcloud/Appunti/
StandardOutput=journal
StandardError=journal
# ~/.config/systemd/user/sync-appunti.timer
[Unit]
Description=Sincronizza appunti ogni 30 minuti
[Timer]
OnCalendar=*:00/30
Persistent=true
[Install]
WantedBy=default.target
# Abilita senza sudo
systemctl --user daemon-reload
systemctl --user enable --now sync-appunti.timer
# Verifica
systemctl --user list-timers
systemctl --user status sync-appunti.timer
# Log
journalctl --user -u sync-appunti.service
Per default i timer utente girano solo quando l'utente è loggato. Per farli girare anche senza sessione attiva:
sudo loginctl enable-linger nomeutente
5.9 Esempio 8 — Condizioni avanzate
# /etc/systemd/system/backup-condizionato.service
[Unit]
Description=Backup solo se il NAS è montato
[Service]
Type=oneshot
User=backup
# Esegui solo se il punto di mount esiste
ConditionPathIsMountPoint=/mnt/nas
# Esegui solo se il file di abilitazione è presente
ConditionPathExists=/etc/backup/abilitato
# Esegui solo se non siamo in modalità container
ConditionVirtualization=false
ExecStart=/opt/scripts/backup-nas.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/backup-condizionato.timer
[Unit]
Description=Backup NAS condizionato
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
.service
| Direttiva | Verifica |
|---|---|
ConditionPathExists=/percorso |
il percorso esiste |
ConditionPathIsDirectory=/percorso |
è una directory |
ConditionPathIsMountPoint=/percorso |
è un mount point |
ConditionFileNotEmpty=/percorso |
il file non è vuoto |
ConditionVirtualization=false |
non siamo in una VM/container |
ConditionACPower=true |
sistema alimentato (non batteria) |
ExecCondition=/bin/test -f /tmp/go |
esegue un comando, procede se esce con 0 |
6. Casi d'uso reali
6.1 Rotazione log personalizzata
# /etc/systemd/system/ruota-log.service
[Unit]
Description=Rotazione log applicazione custom
[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.d/mia-app
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/ruota-log.timer
[Unit]
Description=Rotazione log ogni notte
[Timer]
OnCalendar=daily
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
6.2 Monitoring e alerting periodico
# /etc/systemd/system/check-disco.service
[Unit]
Description=Controllo spazio disco con alert
[Service]
Type=oneshot
ExecStart=/opt/scripts/check-disk-space.sh
# Se il controllo fallisce (exit code != 0), invia notifica
OnFailure=notifica-admin@.service
StandardOutput=journal
StandardError=journal
#!/bin/bash
# /opt/scripts/check-disk-space.sh
SOGLIA=85
HOSTNAME=$(hostname)
while IFS= read -r line; do
UTILIZZO=$(echo "$line" | awk '{print $5}' | sed 's/%//')
PARTIZIONE=$(echo "$line" | awk '{print $6}')
if [ "$UTILIZZO" -gt "$SOGLIA" ]; then
echo "ALERT: $PARTIZIONE al ${UTILIZZO}% su $HOSTNAME" | \
/usr/bin/mail -s "Disco quasi pieno su $HOSTNAME" admin@esempio.com
exit 1
fi
done < <(df -h | grep -vE '^Filesystem|tmpfs|cdrom')
echo "OK: tutti i filesystem sotto la soglia ${SOGLIA}%"
exit 0
# /etc/systemd/system/check-disco.timer
[Unit]
Description=Controllo spazio disco ogni 4 ore
[Timer]
OnCalendar=*-*-* 00/4:00:00
Persistent=true
[Install]
WantedBy=timers.target
6.3 Backup database PostgreSQL
# /etc/systemd/system/backup-postgres.service
[Unit]
Description=Backup notturno PostgreSQL
After=postgresql.service
Requires=postgresql.service
[Service]
Type=oneshot
User=postgres
Group=postgres
Environment="BACKUP_DIR=/var/backups/postgresql"
Environment="RETENTION_DAYS=14"
ExecStart=/bin/bash -c '/usr/bin/pg_dumpall | /usr/bin/gzip > ${BACKUP_DIR}/all_$(date +%%Y%%m%%d_%%H%%M%%S).sql.gz'
ExecStart=/usr/bin/find /var/backups/postgresql -name "*.sql.gz" -mtime +14 -delete
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/backup-postgres.timer
[Unit]
Description=Backup PostgreSQL alle 01:30
[Timer]
OnCalendar=*-*-* 01:30:00
Persistent=true
[Install]
WantedBy=timers.target
6.4 Pulizia /tmp al boot e periodica
# /etc/systemd/system/pulizia-tmp.service
[Unit]
Description=Pulizia file temporanei
[Service]
Type=oneshot
ExecStart=/usr/bin/find /tmp -mindepth 1 -atime +3 -delete
ExecStart=/usr/bin/find /var/tmp -mindepth 1 -atime +30 -delete
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/pulizia-tmp.timer
[Unit]
Description=Pulizia /tmp al boot e poi ogni 24 ore
[Timer]
OnBootSec=10min
OnUnitActiveSec=24h
[Install]
WantedBy=timers.target
7. Gestione e debug
7.1 Comandi essenziali per i timer
# Elenca tutti i timer attivi con prossima esecuzione
systemctl list-timers
# Elenca tutti i timer (anche quelli non attivi)
systemctl list-timers --all
# Abilita e avvia un timer
sudo systemctl daemon-reload
sudo systemctl enable --now nome.timer
# Disabilita e ferma un timer
sudo systemctl disable --now nome.timer
# Stato di un timer specifico
systemctl status nome.timer
# Avvia manualmente il servizio associato (test)
sudo systemctl start nome.service
# Ferma il servizio associato
sudo systemctl stop nome.service
7.2 Leggere i log
# Log del servizio associato al timer
journalctl -u nome.service
# Log in tempo reale
journalctl -u nome.service -f
# Log dall'avvio corrente
journalctl -u nome.service -b
# Log degli ultimi 100 righe
journalctl -u nome.service -n 100
# Log delle ultime 2 ore
journalctl -u nome.service --since "2 hours ago"
# Log sia del timer che del servizio
journalctl -u nome.timer -u nome.service
7.3 Verificare le espressioni calendario
# Verifica e normalizza un'espressione
systemd-analyze calendar "Mon..Fri *-*-* 08:30:00"
systemd-analyze calendar "*:00/15"
systemd-analyze calendar "2026-12-25"
# Output tipico:
# Original form: Mon..Fri *-*-* 08:30:00
# Normalized form: Mon..Fri *-*-* 08:30:00
# Next elapse: Mon 2026-06-09 08:30:00 CEST
# (in UTC): Mon 2026-06-09 06:30:00 UTC
# From now: 1 day 22h left
7.4 Modificare un timer esistente (drop-in)
# Modifica senza toccare il file originale (crea un override)
sudo systemctl edit nome.timer
# Questo crea /etc/systemd/system/nome.timer.d/override.conf
# Esempio di override per cambiare solo l'orario:
# /etc/systemd/system/nome.timer.d/override.conf
[Timer]
OnCalendar=
OnCalendar=*-*-* 05:00:00
# Modifica il file originale (se necessario sostituire tutto)
sudo systemctl edit --full nome.timer
# Dopo qualsiasi modifica
sudo systemctl daemon-reload
sudo systemctl restart nome.timer
7.5 Testare un servizio manualmente
# Prima di mettere in produzione il timer, testa il servizio direttamente
sudo systemctl start nome.service
# Controlla immediatamente lo stato
systemctl status nome.service
# Leggi il log dell'esecuzione
journalctl -u nome.service -n 50 --no-pager
# 1. Crea i file
sudo nano /etc/systemd/system/mio-task.service
sudo nano /etc/systemd/system/mio-task.timer
# 2. Ricarica systemd
sudo systemctl daemon-reload
# 3. Testa il servizio manualmente
sudo systemctl start mio-task.service
systemctl status mio-task.service
journalctl -u mio-task.service -n 20
# 4. Se funziona, abilita il timer
sudo systemctl enable --now mio-task.timer
# 5. Verifica
systemctl list-timers | grep mio-task
7.6 Troubleshooting timer che non partono
# Il timer è attivo?
systemctl is-active nome.timer
# Il timer è abilitato?
systemctl is-enabled nome.timer
# Errori nel file di unità?
systemd-analyze verify /etc/systemd/system/nome.timer
systemd-analyze verify /etc/systemd/system/nome.service
# Il servizio fallisce con permessi?
# Controlla User= e Group= nel .service, e i permessi dei file toccati
# Controlla se ci sono condizioni non soddisfatte
systemctl status nome.service
# Cerca "condition check result" nell'output
# Controlla tutti gli errori recenti
journalctl -u nome.service -p err --no-pager
8. Quando usare uno o l'altro
8.1 Usa cron quando...
- Il task è semplice: una riga, un comando, un output
- Stai lavorando su un sistema senza systemd o molto datato
- Hai bisogno di crontab utente semplici senza privilegi root
- Stai migrando da un sistema vecchio e non vuoi riscrivere tutto
- Il task è non critico e non importa se viene saltato
# Esempio di task perfetto per cron: una riga, semplice, non critico
0 3 * * * /usr/bin/find /tmp -name "*.tmp" -mtime +1 -delete > /dev/null 2>&1
8.2 Usa systemd timer quando...
- Il task è in produzione e deve essere affidabile
- Hai bisogno di logging per il debug e il monitoring
- Il sistema può essere spento all'orario pianificato (
Persistent=true) - Il task ha dipendenze (rete, altro servizio, mount point)
- Vuoi controllare le risorse (CPU, memoria) del processo
- Hai una fleet di server e vuoi distribuire il carico (
RandomizedDelaySec) - Vuoi riattivare il sistema dalla sospensione per eseguire il task
Non devi migrare tutto in una volta. Puoi mantenere cron per i task semplici e usare systemd timer per quelli nuovi o critici. Coesistono senza problemi.
8.3 Tabella di decisione rapida
Task semplice, una riga?
├─ SÌ → usa CRON
└─ NO → continua
Il sistema può essere spento all'orario pianificato?
├─ SÌ → usa SYSTEMD TIMER (Persistent=true)
└─ NO → continua
Hai bisogno di logging e debugging?
├─ SÌ → usa SYSTEMD TIMER
└─ NO → continua
Il task ha dipendenze (rete, DB, mount)?
├─ SÌ → usa SYSTEMD TIMER
└─ NO → entrambi vanno bene, scegli in base alla preferenza
systemd-analyze calendarè il tuo miglior amico per verificare le espressioni prima di metterle in produzione- Usa sempre
Persistent=truenei timer di sistema su workstation e laptop - I timer utente con
systemctl --usersono ottimi per automazioni personali senza sudo - Per i crontab di sistema, preferisci
/etc/cron.d/a/etc/crontabper organizzazione
Guida basata su: ilsoftware.it — Cron vs timer systemd (4 giugno 2026)