Servizi propri e timer
Modulo 6: Servizi e log · Tempo di lettura: 11 minuti
Obiettivo della lezione: Al termine di questa lezione sarà in grado di eseguire un proprio programma come servizio systemd con un utente dedicato, con alcune opzioni di sicurezza, e di pianificare attività con un timer systemd o con cron.
Concetti chiave: file unit, [Unit], [Service], [Install], ExecStart, User=, WorkingDirectory, EnvironmentFile, Restart=, WantedBy, NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, systemd-analyze security, timer, OnCalendar, Persistent, crontab
Perché non usare semplicemente "nohup" o "screen"?
Chi installa un proprio programma su un server (una piccola applicazione web, un bot, uno script che ogni notte svolge un compito) spesso la prima volta lo avvia a mano: node server.js &, oppure in una sessione screen. Funziona finché non si disconnette, il server non si riavvia o il programma non va in crash. A quel punto il programma non c'è più, e nessuno se ne accorge.
Un programma che deve restare sempre in esecuzione dovrebbe essere un servizio systemd. In questo modo:
- si avvia da solo all'accensione;
- systemd lo riavvia se va in crash;
- l'output finisce nel journal (lezione 6.2);
- viene eseguito con un utente scelto da Lei, con i permessi che Lei concede.
Un file unit proprio
Supponiamo che Lei abbia una piccola applicazione Node.js in /opt/factuurapp, in ascolto su 127.0.0.1:3000. Più avanti nginx o Caddy la renderanno raggiungibile dall'esterno (modulo 7).
Passo 1: un utente dedicato senza shell (lezione 3.2). L'applicazione non ha bisogno di essere root né di effettuare l'accesso:
anna@web1:~$ sudo adduser --system --group --home /opt/factuurapp --no-create-home factuurapp
anna@web1:~$ sudo chown -R factuurapp:factuurapp /opt/factuurapp/dataIl codice stesso può restare di proprietà di root (l'applicazione non deve modificare se stessa); solo la cartella in cui scrive appartiene all'utente del servizio.
Passo 2: il file unit. Le unit personalizzate vanno in /etc/systemd/system/:
# /etc/systemd/system/factuurapp.service
[Unit]
Description=Factuurapp (Node.js)
After=network-online.target
Wants=network-online.target
[Service]
User=factuurapp
Group=factuurapp
WorkingDirectory=/opt/factuurapp
EnvironmentFile=/etc/factuurapp/env
ExecStart=/usr/bin/node /opt/factuurapp/server.js
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/factuurapp/data
[Install]
WantedBy=multi-user.targetI tre blocchi:
- [Unit]: descrizione e ordine di avvio.
After=network-online.targetinsieme aWants=fa sì che il servizio parta solo quando la rete è disponibile. - [Service]: come viene eseguito il programma.
ExecStartcon il percorso completo (systemd non usa un percorso di ricerca come la Sua shell).User=eGroup=stabiliscono con quale utente.EnvironmentFilelegge impostazioni come la password di un database da un file separato, così non compaiono nella unit. - [Install]: che cosa fa
enable.WantedBy=multi-user.targetsignifica: avviarsi insieme a un normale avvio del sistema.
Passo 3: segreti separati, con permessi restrittivi.
anna@web1:~$ sudo mkdir /etc/factuurapp
anna@web1:~$ sudo install -m 600 -o root -g root /dev/null /etc/factuurapp/env
anna@web1:~$ sudoedit /etc/factuurapp/envIl contenuto può essere ad esempio DATABASE_URL=postgres://factuur:...@127.0.0.1/factuur. systemd legge il file come root prima di passare all'utente del servizio, quindi 600 di proprietà di root è sufficiente.
Passo 4: caricare, avviare, verificare.
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now factuurapp
anna@web1:~$ systemctl status factuurapp
anna@web1:~$ journalctl -u factuurapp -fOpzioni di sicurezza: poco sforzo, grande differenza
Le quattro righe sotto RestartSec nell'esempio limitano ciò che il servizio può fare, anche se qualcuno trova una falla nella Sua applicazione:
| Opzione | Effetto |
|---|---|
NoNewPrivileges=true | il processo e i suoi processi figli non possono mai ottenere più privilegi (nemmeno tramite sudo o programmi setuid) |
ProtectSystem=strict | l'intero file system è in sola lettura per il servizio, tranne ciò che Lei consente con ReadWritePaths= |
ProtectHome=true | /home, /root e /run/user sono invisibili |
PrivateTmp=true | una /tmp propria e vuota, che gli altri programmi non vedono |
systemd può valutare la Sua unit:
anna@web1:~$ systemd-analyze security factuurapp
...
→ Overall exposure level for factuurapp.service: 4.9 OK 🙂Più basso è il numero, meglio è. Un servizio senza opzioni ottiene circa 9 ("UNSAFE"). Non è necessario arrivare a zero: ogni opzione aggiunta può bloccare qualcosa di cui l'applicazione ha bisogno. Le aggiunga una alla volta e verifichi dopo ogni passo. Se qualcosa smette di funzionare, di solito lo trova nel journal ("Read-only file system", "Permission denied").
Pianificare attività: i timer
Per qualcosa che deve avvenire a orari fissi (un backup, uno script di pulizia, un report) serve un timer. Un timer systemd è composto da due unit con lo stesso nome: un .service che dice che cosa deve accadere e un .timer che dice quando.
# /etc/systemd/system/opruimen.service
[Unit]
Description=Oude exportbestanden opruimen
[Service]
Type=oneshot
User=factuurapp
ExecStart=/usr/bin/find /opt/factuurapp/data/export -type f -mtime +30 -delete# /etc/systemd/system/opruimen.timer
[Unit]
Description=Dagelijks oude exportbestanden opruimen
[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.targetType=oneshot significa: il programma viene eseguito e poi termina. OnCalendar è la pianificazione: ogni giorno alle 03:15. Può anche scrivere daily, weekly o Mon *-*-* 02:00; systemd-analyze calendar "Mon *-*-* 02:00" mostra quando sarà la prossima esecuzione. Persistent=true è importante: se alle 03:15 il server era spento, l'attività viene eseguita subito dopo l'avvio. RandomizedDelaySec distribuisce un po' il momento di avvio.
Si attiva il timer, non il service:
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now opruimen.timer
anna@web1:~$ systemctl list-timers opruimen
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-11-10 03:19:42 CET 15h left - - opruimen.timer opruimen.service
anna@web1:~$ sudo systemctl start opruimen.service # ora testare una volta manualmente
anna@web1:~$ journalctl -u opruimen.serviceE cron?
cron è lo strumento più vecchio e più semplice, e funziona ancora benissimo. Una riga nella crontab di un utente (crontab -e):
15 3 * * * /usr/local/bin/opruimen.shI cinque campi sono minuto, ora, giorno del mese, mese e giorno della settimana. Vantaggi e svantaggi a confronto:
| cron | timer systemd | |
|---|---|---|
| Configurazione | una riga | due file |
| Esecuzione mancata (server spento) | viene saltata | recuperata con Persistent=true |
| Output ed errori | via email, se il server può inviare email; altrimenti persi | nel journal |
| Opzioni di sicurezza, utente | tramite lo script stesso | tutto ciò che offre systemd (vedi sopra) |
| Vedere quando è stato eseguito | difficile | systemctl list-timers e il journal |
Per un server gestito seriamente i timer sono la scelta migliore, soprattutto per i backup (lezione 8.2): Lei vuole sapere con certezza che sono stati eseguiti e che cosa hanno restituito. Una riga di cron che fallisce in silenzio la scopre solo quando ha bisogno del backup.
Su AlmaLinux e Rocky Linux
Unit e timer funzionano esattamente allo stesso modo. Una differenza: SELinux può bloccare un servizio proprio che legge o scrive file in una posizione che SELinux non si aspetta, oppure che ascolta su una porta insolita. Il messaggio si trova in /var/log/audit/audit.log. Non disattivi SELinux per risolvere il problema; cerchi l'etichetta o la regola corretta (ad esempio con semanage).
Riepilogo
- Un programma che deve restare sempre in esecuzione dovrebbe essere un servizio systemd: avvio automatico, riavvio in caso di crash, log nel journal.
- Unit personalizzate in
/etc/systemd/system/, conUser=un utente dedicato senza shell, segreti in unEnvironmentFilecon permessi 600. NoNewPrivileges,ProtectSystem=strictconReadWritePaths,ProtectHomeePrivateTmplimitano i danni in caso di falla;systemd-analyze securityvaluta la unit.- Un timer è un
.servicepiù un.timerconOnCalendar;Persistent=truerecupera le esecuzioni mancate. Attivi il timer, non il service. - cron funziona ancora, ma i timer offrono log migliori e non saltano esecuzioni.
Quiz
Esercizio
Esercizio O6.3: Un servizio proprio con timer. Crei sul Suo server un piccolo servizio proprio con un timer, ad esempio uno script che ogni notte scrive lo spazio libero su disco e systemctl --failed in un file o nel journal. Usi un utente dedicato, Type=oneshot, almeno due opzioni di sicurezza e Persistent=true. Verifichi con systemctl start, controlli l'output nel journal e annoti il punteggio di systemd-analyze security. Incolli il Suo .service e il Suo .timer e risponda alle domande della checklist.
Esercizio O6.3: Un servizio proprio con timer
Questo esercizio lo svolge da sé; nessuno lo verifica. Spunti ciò che ha fatto e segni l'esercizio come completato.
Con Persistent=true l'attività viene eseguita comunque dopo l'avvio. Un punteggio inferiore a 6 va bene per un primo servizio; permessi di scrittura solo tramite ReadWritePaths.
Le Sue spunte e note restano solo in questo browser; non le inviamo da nessuna parte. Se ha effettuato l'accesso, conserviamo in I miei corsi solo il fatto che ha svolto l'esercizio.
Per approfondire
- Guida DirectAdmin 15.1: Processi Cron: eseguire compiti automaticamente
- Manuale
systemd.exec(5)(User=, ProtectSystem=, NoNewPrivileges=): https://manpages.debian.org/trixie/systemd/systemd.exec.5.en.html - Manuale
systemd.timer(5)(OnCalendar=, Persistent=): https://manpages.debian.org/trixie/systemd/systemd.timer.5.en.html - Manuale
systemd.time(7)(pianificazioni): https://manpages.debian.org/trixie/systemd/systemd.time.7.en.html
