Come creare un servizio systemd su VPS
Impara a configurare un file .service per avviare programmi all'avvio, gestire il restart automatico in caso di crash e consultare i log tramite journalctl.
Cos'è un servizio systemd e perché è necessario
Un servizio systemd è un piccolo file di testo che indica al server come eseguire un programma: avviarlo all'avvio, riavviarlo in caso di crash e inviare l'output al log di sistema. Questo è il suo unico compito. Un programma avviato manualmente in una sessione SSH termina non appena si effettua il logout o il server si riavvia. Un programma configurato come servizio systemd continua a essere eseguito, poiché il server lo gestisce direttamente invece della shell.
systemd è il sistema di init su Ubuntu, Debian, Fedora e sulla maggior parte dei server Linux moderni. È il primo processo ad avviarsi e supervisiona tutti gli altri. Quando si scrive un file di servizio, si affida il programma a questo supervisore. Questa guida mostra l'unità minima funzionante, le tre sezioni presenti in ogni unità, come attivarla e leggerne i log, come eseguirla tramite un timer e come limitare i privilegi per farla girare con il minimo accesso necessario.
Il servizio più semplice funzionante
Un file di servizio si trova in /etc/systemd/system/, ha estensione .service e richiede solo poche righe. Creane uno per un programma in /usr/local/bin/myapp:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetQuesta è un'unità completa e funzionante. ExecStart è il comando da eseguire. WantedBy=multi-user.target indica di avviare il servizio quando il server raggiunge la modalità multi-user normale; questo permette l'avvio automatico al boot. Tutto il resto è ottimizzazione.
Le tre sezioni e la loro funzione
Ogni unit file è diviso in sezioni delimitate da parentesi quadre. Un service ne utilizza tre.
[Unit] descrive il service e le sue relazioni. Le due righe più utilizzate sono:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription è l'etichetta leggibile dall'utente visibile in systemctl status. After=network-online.target indica a systemd di non avviare il programma finché la rete non è attiva; questo è fondamentale per qualsiasi processo che utilizzi una porta o effettui connessioni in uscita.
[Service] definisce come viene eseguito il programma. Qui si trovano la maggior parte delle impostazioni:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp esegue il programma con un account non privilegiato invece di root; questa è la riga più importante per la sicurezza. Restart=on-failure e RestartSec=5 hanno una sezione dedicata sotto, poiché sono il motivo principale per cui si crea un service.
[Install] definisce cosa accade quando si abilita il service:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target è ciò che collega il service all'avvio quando si esegue systemctl enable. Senza una sezione [Install], il service può essere avviato manualmente ma non si avvierà automaticamente dopo un reboot.
Attivalo e monitoralo
Dopo aver scritto o modificato un unit file, ricarica systemd per applicare le modifiche. Successivamente, abilita e avvia il servizio con un unico comando:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload è il passaggio che spesso viene dimenticato: systemd memorizza i file unit in cache, quindi una modifica non ha effetto finché non ricarichi la configurazione. enable --now abilita il servizio all'avvio e lo avvia immediatamente. Verifica l'operazione:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)I risultati corretti sono Active: active (running) e enabled. Per leggere l'output del programma, interroga il journal filtrando solo per questa unit:
sudo journalctl -u myapp.service -fIl comando -f segue le nuove righe man mano che arrivano, in modo simile a tail -f. Tutto ciò che il programma scrive su standard output o standard error viene registrato qui, senza necessità di configurare il logging.
Restart su errore, il motivo per cui sei qui
Il vantaggio principale di un service è che systemd riavvia il programma quando termina. Due opzioni lo permettono:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure riavvia il programma se esce con un codice diverso da zero o se crasha a causa di un segnale come SIGKILL o SIGSEGV. Un'uscita pulita, o un arresto tramite SIGTERM, SIGINT, SIGHUP o SIGPIPE, non attiva il riavvio. RestartSec=5 attende cinque secondi tra i tentativi, evitando che un programma che crasha istantaneamente entri in un loop infinito. Verifica il funzionamento terminando il processo e osservando systemd che lo riavvia. Usa SIGKILL: il default SIGTERM è considerato un arresto pulito, quindi on-failure non riavvierebbe il service:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceEntro cinque secondi, lo status mostrerà un nuovo Main PID e active (running). Questa è la funzione principale, ed è il motivo per cui un service è preferibile rispetto a un programma eseguito in tmux o screen.
Eseguilo come utente non privilegiato e applica l'hardening
Un servizio che gira come root può compromettere l'intero server se il programma viene sfruttato. Eseguilo con un utente dedicato e utilizza le direttive di systemd per limitarne l'accesso. Per prima cosa, crea un account di sistema senza login e senza home directory:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappSuccessivamente, imposta User=myapp e aggiungi le direttive di hardening in [Service]:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueOgni riga rimuove una funzionalità non necessaria al programma. NoNewPrivileges=true impedisce al processo di acquisire nuovi privilegi, anche tramite un binary setuid. PrivateTmp=true fornisce un /tmp privato che nessun altro processo può vedere. ProtectSystem=strict rende l'intero filesystem in sola lettura, fatta eccezione per i percorsi specificati con ReadWritePaths=. ProtectHome=true nasconde completamente /home. Questo approccio segue il principio del minimo privilegio, analogamente all'uso di un firewall: fornisci al servizio solo ciò di cui ha bisogno. Se hai letto la guida su chiudere il gap del firewall IPv6 su un VPS, questa è la parte relativa all'host dello stesso concetto. Per un servizio esposto su internet, combina questo hardening con Fail2ban davanti a SSH e un firewall con policy default-deny.
Invece di digitare manualmente ogni comando rischiando errori, genera un file unit completo e protetto e copialo:
Timers: il cron moderno
Un timer di systemd esegue un servizio secondo un programma prestabilito; è il sostituto moderno dei cron job. Un timer è composto da due file: un .service che esegue il lavoro e un .timer che ne definisce l'orario. Supponiamo di voler eseguire un backup ogni giorno alle 3:00. Il servizio esegue il compito una volta e termina:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot indica a systemd che il programma viene eseguito, termina e si chiude, invece di rimanere in memoria. Il timer lo pianifica:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 indica le 3:00 di ogni giorno. È possibile testare qualsiasi espressione di calendario con systemd-analyze calendar "*-*-* 03:00:00", che conferma la corretta analisi sintattica e stampa le prossime esecuzioni. Persistent=true esegue un lavoro saltato non appena il server viene riavviato se era spento alle 3:00, funzionalità non disponibile in cron. Si noti che un timer viene abilitato tramite timers.target e non tramite multi-user.target. Abilitare il timer, non il servizio:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers mostra ogni timer con la prossima e l'ultima esecuzione, permettendo di vedere immediatamente quando verrà eseguito il prossimo job. Il generatore sopra citato crea automaticamente la coppia .service e .timer quando si attiva la modalità timer. Rispetto a una riga cron, un timer fornisce log reali nel journal, le stesse direttive di hardening di qualsiasi servizio e la gestione dei job saltati tramite Persistent=true. Cron è ancora adatto per compiti semplici; un timer è lo strumento migliore per compiti critici.
FAQ
Qual è la differenza tra un servizio systemd e un cron job?
Un servizio mantiene attivo un programma in esecuzione continua: si avvia all'avvio del sistema, si riavvia in caso di errore e registra i log nel journal. Un cron job esegue un comando breve secondo un programma prestabilito e poi termina. Se è necessaria la pianificazione ma si desiderano anche i log del journal, l'hardening e il recupero delle esecuzioni mancate, utilizzare un systemd timer. Questo accoppia un programma di .timer con un servizio oneshot e sostituisce cron per la maggior parte dei compiti server.
Dove devo inserire il mio file di servizio systemd?
Inserire le proprie unità in /etc/systemd/system/, con un nome che termina in .service. Quella directory è destinata alle unità aggiunte dall'amministratore e ha la priorità sulle unità incluse nei pacchetti in /lib/systemd/system/. Dopo aver creato o modificato un file, eseguire sudo systemctl daemon-reload affinché systemd rilevi la modifica.
Come posso far riavviare un servizio se va in crash?
Aggiungere Restart=on-failure e RestartSec=5 alla sezione [Service], quindi eseguire sudo systemctl daemon-reload e riavviare il servizio. systemd riavvia il programma quando questo termina con un codice di uscita diverso da zero o quando muore a causa di un segnale di crash, attendendo cinque secondi tra i tentativi. Testare con sudo systemctl kill -s SIGKILL myapp.service — SIGTERM, il segnale predefinito, è considerato un arresto pulito e non attiva on-failure — e verificare che systemctl status mostri un nuovo PID entro pochi secondi.
Come posso eseguire un servizio systemd come utente non-root?
Creare un account di sistema con sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp, quindi aggiungere User=myapp alla sezione [Service]. Aggiungere NoNewPrivileges=true, PrivateTmp=true e ProtectSystem=strict affinché il processo operi con il minimo livello di accesso necessario. Eseguire il servizio come utente non privilegiato è la modifica più importante per aumentarne la sicurezza.
Perché il mio servizio non si è avviato?
Eseguire systemctl status myapp.service per il riepilogo e journalctl -u myapp.service per l'output completo. Le cause più comuni sono un percorso errato in ExecStart, un WorkingDirectory mancante, un errore di permessi perché User= non può leggere un file, o un sudo systemctl daemon-reload dimenticato dopo una modifica. Il journal mostra il messaggio di errore del programma stesso, che solitamente indica direttamente il problema.