Cron vs systemd timer

⏱️ Cron vs Systemd Timer — Guida Pratica alla Pianificazione su Linux

Sintesi

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

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
Formato /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
Il nome conta

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
Regola pratica

  • 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 modernosystemd 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

Buone pratiche per gli script cron

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
Timer utente e login

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
Condizioni disponibili in .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
Workflow di creazione e test

# 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...

# 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...

Migrazione da cron a systemd timer

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

Note finali

  • systemd-analyze calendar è il tuo miglior amico per verificare le espressioni prima di metterle in produzione
  • Usa sempre Persistent=true nei timer di sistema su workstation e laptop
  • I timer utente con systemctl --user sono ottimi per automazioni personali senza sudo
  • Per i crontab di sistema, preferisci /etc/cron.d/ a /etc/crontab per organizzazione

Guida basata su: ilsoftware.it — Cron vs timer systemd (4 giugno 2026)