SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

systemd Type=: simple, forking o notify? Guida

L'unità resta active ma il demone è terminato? Scegli Type= tra simple, exec, forking, oneshot e notify e individua il vero PID principale.

Perché systemd segnala un'unità come attiva quando il processo è terminato

Un'unità di servizio systemd resta active finché è in esecuzione l'unico processo che systemd considera il processo principale. Il valore Type= nella sezione [Service] determina quale processo sia. Se si sceglie il valore errato, systemd finisce per monitorare un wrapper della shell o un processo padre di breve durata, mentre il demone che interessa termina all'interno della stessa unità. L'unità descrive correttamente lo stato del processo che le è stato indicato di monitorare.

Modificare la policy di riavvio non risolve il problema. Restart= viene applicato quando termina il processo principale, quindi Restart=always non viene mai attivato finché il PID principale (identificatore del processo) appartiene a un processo ancora in esecuzione. Correggere prima Type=. Il comportamento di systemd dopo la terminazione effettiva del processo principale è una decisione separata, descritta in la guida a Restart= e RestartSec=.

Che cosa determina effettivamente Type=

Ogni valore Type= risponde contemporaneamente a due domande: quando systemd può considerare avviata questa unità e quale processo è quello principale.

La prima risposta controlla l'ordine di avvio. Un'unità che indica la tua in After= attende che systemd consideri avviata la tua unità. Un Type= che segnala «avviato» troppo presto consente alle unità dipendenti di avviarsi prima che il servizio possa rispondere.

La seconda risposta controlla la supervisione. systemd inserisce ogni processo avviato da un'unità in un cgroup (control group), una funzionalità del kernel che raggruppa i processi affinché possano essere limitati e terminati insieme. Il cgroup è il meccanismo con cui systemctl stop esegue la pulizia: KillMode= è impostato per impostazione predefinita su control-group, quindi l'arresto di un'unità invia un segnale a ogni processo al suo interno. Il PID principale ha un ambito più ristretto. È il singolo processo la cui terminazione conclude l'unità e il cui codice di uscita diventa il risultato dell'unità. La confusione nasce quando si interpreta il cgroup come se fosse il PID principale.

Type=simple segnala l’avvio prima dell’esecuzione del binario

Type=simple è il valore predefinito quando è impostato ExecStart= e non sono presenti né Type= né BusName=. systemd crea il processo, considera immediatamente avviata l’unità e tratta quel processo come PID principale. Le unità dipendenti vengono avviate subito, prima ancora che il binario del servizio sia stato eseguito.

Questo ultimo dettaglio spiega un problema comune. Anche un errore di battitura nel percorso ExecStart= produce un job di avvio completato correttamente. Il problema viene rilevato un momento dopo, quando l’esecuzione ha esito negativo. systemd registra questo caso con il codice di uscita 203, che nella propria tabella è indicato come EXEC e definito come errore nell’esecuzione del binario del servizio. Di conseguenza, il ritorno di systemctl start senza errori non dimostra che il binario esista.

Usare simple per un programma che rimane in primo piano e non passa autonomamente in background. Questo vale per la maggior parte dei daemon moderni e per quasi tutti i programmi scritti direttamente dall’amministratore.

Type=exec attende l'avvio effettivo del programma

Type=exec corrisponde a simple con un passaggio aggiuntivo. systemd considera l'unità avviata solo dopo che il fork e l'esecuzione del binario sono riusciti. Se il binario non esiste o non è possibile risolvere un User=, il job di avvio fallisce direttamente, invece di segnalare il successo e fallire silenziosamente un momento dopo.

Type=exec è disponibile in systemd 240, quindi tutte le distribuzioni server attuali lo includono. Ubuntu 24.04 include systemd 255 e Debian 13 include systemd 257, alla data di agosto 2026. Verifica la tua versione con systemctl --version.

Il costo consiste in un passaggio aggiuntivo di sincronizzazione all'avvio. Il vantaggio è uno stato di uscita affidabile restituito da systemctl start. Per un programma in primo piano, preferisci exec a simple.

Type=forking e come può andare perso il PID principale

Type=forking comunica a systemd che il processo in ExecStart= creerà un processo figlio e poi terminerà intenzionalmente. systemd attende la terminazione del primo processo e solo dopo considera avviata l'unità. Il processo figlio rimasto è il demone. Questa modalità risale all'epoca di SysV, quando nessun componente supervisionava un demone dopo la restituzione dello script init e un file PID era l'unica traccia del processo in esecuzione. Si tratta di una limitazione che è alla base di perché systemd ha sostituito gli script init.

La difficoltà riguarda l'identità del processo. Il processo avviato da systemd è terminato, quindi systemd deve determinare quale processo rimasto sia quello principale. Impostare PIDFile= sul file scritto dal demone, normalmente un percorso sotto /run, in modo che systemd possa leggerne il PID. systemd verifica inoltre che il PID contenuto nel file corrisponda a un processo già appartenente a questo servizio. In questo modo un file obsoleto che indica un processo non correlato viene rifiutato anziché considerato attendibile.

Senza PIDFile= si applica GuessMainPID=, il cui valore predefinito è yes. Questa stima è affidabile soltanto quando il servizio si stabilizza in un unico processo. La documentazione indica chiaramente il limite: se il demone è composto da più processi, la stima può essere errata e il rilevamento dei guasti smette di funzionare. Un'unità può anche ritrovarsi con un PID principale pari a 0. In tal caso systemd non ha alcun processo da supervisionare.

La maggior parte dei demoni che creano processi figli dispone anche di un'opzione per rimanere in primo piano. Usare questa opzione con Type=exec ed eliminare la riga PIDFile=. Meno componenti da gestire significano meno possibilità di perdere il PID.

Type=oneshot per le attività che terminano

Type=oneshot prevede che il processo venga eseguito e termini. systemd considera l’unità avviata solo dopo la sua terminazione; per questo oneshot è il tipo adatto a tutto ciò che deve essere completato prima che un’altra unità possa procedere. È anche il valore predefinito implicito quando un’unità non specifica né Type= né ExecStart=.

Due comportamenti sono specifici di oneshot. È l’unico tipo che accetta più righe ExecStart=, eseguite nell’ordine in cui sono definite. Per impostazione predefinita, inoltre, il timeout di avvio è disabilitato. Di conseguenza, un oneshot che resta bloccato attende per sempre, a meno che non si imposti manualmente TimeoutStartSec=.

Dopo la terminazione del processo, l’unità torna allo stato inactive. RemainAfterExit=yes la mantiene nello stato active anche quando non è in esecuzione alcun processo. Questo è il comportamento intenzionale descritto dal sintomo all’inizio di questa pagina ed è corretto quando il compito dell’unità consiste nel lasciare uno stato persistente invece di mantenere in esecuzione un processo, ad esempio caricare un ruleset del firewall o avviare uno stack di container. È il modello alla base di uno stack Docker Compose che torna disponibile dopo un riavvio, in cui l’unità esegue il comando compose, termina e resta active perché i container avviati continuano a esistere indipendentemente dall’unità. Un’unità oneshot viene inoltre attivata da una pianificazione: è l’altra parte di eseguire un’attività con un timer systemd invece di cron.

Type=notify consente al servizio di segnalare quando è pronto

Type=notify trasferisce la decisione al servizio. systemd mantiene aperto il job di avvio finché il processo non invia READY=1 tramite un socket Unix il cui percorso viene ricevuto nella variabile d’ambiente NOTIFY_SOCKET. L’interfaccia C è sd_notify(3) e molti server la supportano già.

Questa è la risposta corretta alla domanda «è stato avviato?». simple e exec segnalano l’avvio prima che il servizio abbia letto la configurazione o aperto il socket in ascolto. Di conseguenza, un’unità dipendente può avviarsi troppo presto e fallire la prima connessione. notify segnala l’avvio nel momento in cui il servizio stesso dichiara di essere pronto.

systemd accetta questo messaggio soltanto dal processo principale. È questo il significato di NotifyAccess=main, che è implicato da Type=notify. Se il messaggio proviene da un processo figlio o da un processo helper, impostare NotifyAccess=all. Uno script shell può chiamare systemd-notify --ready, ma il comando viene eseguito come processo separato di breve durata. Sono quindi necessari NotifyAccess=all e systemd potrebbe non riuscire ad attribuire un messaggio il cui mittente è già terminato. Un servizio che implementa direttamente il protocollo è più affidabile.

È utile conoscere anche due impostazioni correlate. Type=notify-reload, disponibile a partire da systemd 253, estende lo stesso handshake alle operazioni di reload. In questo modo systemctl reload restituisce il controllo quando il servizio segnala che il reload è terminato, invece di restituirlo quando il segnale è stato inviato. WatchdogSec= richiede a un servizio che invia notifiche di mandare un messaggio di keep-alive a intervalli regolari. systemd considera il mancato rispetto della scadenza un errore.

Type=dbus e Type=idle

Type=dbus attende che il servizio acquisisca un nome su D-Bus, il message bus utilizzato dai servizi di sistema e desktop per comunicare tra loro. Richiede BusName= e diventa il valore predefinito non appena viene impostato BusName=. Usatelo solo per un servizio che registra effettivamente un nome sul bus.

Type=idle si comporta come simple, ma ritarda l'esecuzione del programma finché i job in coda non sono stati distribuiti, con un limite massimo di cinque secondi. Serve a evitare che l'output della console durante il boot si sovrapponga ai messaggi di stato. Non è uno strumento per definire l'ordine di avvio e non va usato per un servizio normale.

Perché uno script wrapper lascia systemd associato al PID errato

Ecco la struttura che produce il sintomo originale.

[Service]
Type=simple
ExecStart=/opt/app/run.sh
#!/bin/bash
export APP_ENV=production
/opt/app/bin/server --config /etc/app.yaml &
/opt/app/bin/exporter --port 9101

systemd registra la shell come PID principale. La shell rimane in esecuzione mentre exporter viene eseguito in primo piano. Se server termina, la shell non se ne accorge. Di conseguenza, il PID principale è ancora attivo, l'unità è ancora active e Restart= non ha nulla su cui intervenire. Entrambi i processi rimangono sempre nel cgroup dell'unità, quindi systemctl stop continua a eseguire correttamente la pulizia. Si è interrotta la supervisione, non la pulizia.

La correzione dipende dal numero effettivo di processi a esecuzione prolungata gestiti dall'unità.

Se ce n'è uno, sostituisci la shell con il processo.

#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yaml

exec sostituisce la shell con il programma indicato e mantiene lo stesso PID. Il PID registrato da systemd appartiene quindi al daemon. Una soluzione ancora migliore consiste nel rimuovere il wrapper. Environment= e EnvironmentFile= trasferiscono le variabili, mentre ExecStartPre= esegue il passaggio di configurazione. In questo modo systemd può avviare direttamente il daemon e conoscerne il PID per costruzione.

Se sono due, nessun singolo PID rappresenta l'unità. Dividili in due unità e stabilisci l'ordine con After= e Wants=. Un'unità per processo è la struttura che systemd supervisiona meglio ed è l'unico modo per assegnare a ciascun processo un comportamento di riavvio indipendente.

Modifiche apportate da ExitType=cgroup

ExitType= è stato aggiunto in systemd 250. Il valore predefinito è main: l'unità viene considerata arrestata quando termina il processo principale. Con ExitType=cgroup, l'unità viene considerata in esecuzione finché un processo qualsiasi del relativo cgroup è ancora attivo.

[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcher

Questa opzione risolve un problema specifico. Un launcher che avvia il lavoro effettivo e poi termina, con ExitType=main, farebbe considerare l'unità arrestata a systemd, che terminerebbe i processi ancora attivi. Con ExitType=cgroup, invece, l'unità segue l'intero gruppo.

È importante chiarire cosa non risolve. ExitType=cgroup mantiene attiva un'unità finché è in esecuzione almeno un processo. Di conseguenza, un'unità che contiene due daemon resta attiva dopo l'arresto di uno dei due. Questa opzione risolve il caso del launcher. Non trasforma un'unità in un supervisore di più processi indipendenti. ExitType= non può inoltre essere combinato con Type=oneshot.

Anche la contabilizzazione delle risorse avviene nel cgroup. Di conseguenza, limiti come MemoryMax= e CPUQuota= si applicano a ogni processo avviato dall'unità, indipendentemente da ciò che Type= indica per il PID principale. Questo aspetto è descritto in limitare memoria e CPU di un servizio con systemd.

Come individuare il processo che systemd sta effettivamente monitorando

Esegui questi controlli, nell'ordine, sull'unità che stai analizzando. Leggi ciò che systemd ha caricato, poi ciò che monitora, quindi confronta i risultati con la tabella dei processi.

systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.service

systemctl cat stampa il file dell'unità insieme a ogni drop-in applicabile, quindi mostra ciò che systemd ha caricato invece del file che ricordi di avere modificato. systemctl show stampa i valori effettivi, inclusi i valori predefiniti che non avevi annotato. Prima di procedere, annota il valore di MainPID.

systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"

systemd-cgls elenca tutti i processi presenti nel cgroup dell'unità. La riga ps descrive l'unico processo supervisionato da systemd. Leggi insieme i due risultati. Un valore MainPID pari a 0 significa che systemd non ha alcun processo da monitorare. Un valore MainPID che punta a una shell, mentre il cgroup contiene anche il tuo demone, indica il caso del wrapper descritto sopra. Se il cgroup contiene più processi del previsto, è coinvolto un launcher oppure un demone che crea processi figli.

systemctl status app.service
journalctl -u app.service -b

systemctl status stampa insieme la riga di stato e l'albero del cgroup, quindi spesso risponde a entrambe le domande. journalctl -u, limitato a questo avvio tramite -b, mostra gli eventi di avvio e arresto registrati da systemd per l'unità, inclusi i codici di uscita rilevati. Se il demone scrive in un proprio file di log invece che nel journal, leggi anche quel file, perché systemd può registrare soltanto ciò che riceve.

Quando modifichi Type=, esegui il reload e riavvia l'unità.

systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service

systemd-analyze verify analizza il file e segnala le impostazioni che non può accettare. daemon-reload fa rileggere a systemd i file delle unità dal disco. Una modifica a Type= non si applica a un'unità già in esecuzione, quindi il riavvio è necessario.

Poi verifica la modifica. Recupera il PID del processo che ti interessa effettivamente da systemd-cgls e terminane l'esecuzione. Esegui subito dopo systemctl is-active app.service. Se Type= è corretto, l'unità esce dallo stato active. Se rimane active, systemd sta ancora monitorando qualcos'altro.

Quale Type= di systemd utilizzare

  • Un programma che resta in primo piano: Type=exec.
  • Un programma che supporta la notifica di disponibilità: Type=notify, e notify-reload se conferma anche i reload.
  • Un daemon che deve spostarsi in background: Type=forking con PIDFile=, oppure la relativa opzione per il primo piano con Type=exec.
  • Uno script che esegue un'attività e termina: Type=oneshot, oltre a RemainAfterExit=yes quando l'obiettivo era lasciare uno stato persistente.
  • Un launcher che termina mentre i processi figli continuano a essere eseguiti: Type=simple con ExitType=cgroup.

Se non sai quale utilizzare per un daemon di terze parti, leggi prima il relativo unit file fornito dal pacchetto. Eseguendo systemctl cat su una unità distribuita dal sistema puoi vedere il valore di Type= scelto dal progetto upstream. Questa scelta è stata verificata da più persone rispetto alla tua.

FAQ

Perché la mia unit systemd rimane attiva anche se il processo è terminato?

Perché il processo che systemd considera principale è ancora attivo. systemd monitora un solo PID per servizio, scelto in base a Type=, invece di monitorare ogni processo nel cgroup della unit. Uno script wrapper avviato con Type=simple è la causa più comune: la shell è il PID principale, quindi la unit rimane attiva quando termina un demone avviato in background dalla shell. Eseguire systemctl show -p MainPID app.service, quindi elencare il cgroup della unit con systemd-cgls --unit=app.service e confrontare i due risultati.

Qual è la differenza tra Type=simple e Type=exec?

Type=simple considera la unit avviata non appena systemd ha creato il processo, prima dell'esecuzione del binario. Di conseguenza, un percorso errato in ExecStart= produce comunque un job di avvio completato correttamente, seguito da un errore. Type=exec attende che l'esecuzione abbia esito positivo, quindi il job di avvio segnala direttamente l'errore. Entrambi considerano lo stesso processo come PID principale. Type=exec richiede systemd 240 o versioni successive.

Devo usare ancora PIDFile= con Type=forking?

Sì, ogni volta che il demone ne scrive uno. In sua assenza, systemd ricorre a GuessMainPID=, che è una stima ed è affidabile soltanto per un servizio che si stabilizza in un singolo processo. Quando la stima è errata o non è possibile effettuarla, il rilevamento degli errori e il riavvio automatico smettono di funzionare per quella unit. Impostare PIDFile= sul percorso esatto in cui il demone scrive il file, normalmente sotto /run.

Quando devo usare RemainAfterExit=yes?

Quando lo scopo della unit è modificare lo stato del sistema, non mantenere in esecuzione un processo. Una unit Type=oneshot che carica le regole del firewall o avvia uno stack di container termina non appena completa il proprio lavoro. Senza RemainAfterExit=yes, la unit passa allo stato inattivo: systemctl stop non ha nulla da arrestare e non può eseguire una pulizia ExecStop=. Con questa impostazione, la unit rimane attiva senza processi, come previsto in questo caso.

La modifica di Type= richiede daemon-reload?

Sì, e richiede anche il riavvio della unit. systemctl daemon-reload impone a systemd di rileggere dai file system le unit, ma un'istanza in esecuzione continua a usare il Type= con cui è stata avviata. Eseguire sudo systemctl daemon-reload e quindi sudo systemctl restart app.service prima dei test; in caso contrario si sta ancora osservando il comportamento di supervisione precedente.