systemd Type=: simple, forking ou notify?
A unidade fica ativa, mas o daemon sumiu? Entenda simple, exec, forking, oneshot e notify no systemd e descubra qual e o PID principal real.
Por que o systemd informa que uma unidade está ativa quando o processo terminou
Uma unidade de serviço do systemd permanece active enquanto o único processo que o systemd considera o processo principal está ativo, e Type= na seção [Service] determina qual é esse processo. Se escolher o valor errado, o systemd acaba por monitorizar um wrapper de shell ou um processo pai de curta duração, enquanto o daemon que pretende monitorizar termina dentro da mesma unidade. A unidade está a indicar corretamente o estado do processo que foi configurada para monitorizar.
Alterar a política de reinício não resolve este problema. Restart= atua quando o processo principal termina, portanto Restart=always nunca é acionado enquanto o PID principal (identificador do processo) pertencer a algo que ainda esteja em execução. Corrija primeiro Type=. O que o systemd faz depois de o processo principal terminar de facto é uma decisão separada, abordada em guia sobre Restart= e RestartSec=.
O que Type= realmente define
Cada valor de Type= responde a duas perguntas ao mesmo tempo: quando o systemd pode considerar esta unidade iniciada e qual processo é o principal.
A primeira resposta controla a ordem de inicialização. Uma unidade que referencia a sua em After= espera até o systemd considerar que a sua foi iniciada. Um Type= que informa "started" demasiado cedo permite que as unidades dependentes sejam executadas antes de o seu serviço poder responder.
A segunda resposta controla a supervisão. O systemd coloca cada processo criado por uma unidade num cgroup (grupo de controlo), uma funcionalidade do kernel que agrupa processos para que possam ser limitados e terminados em conjunto. É assim que systemctl stop faz a limpeza: KillMode= usa control-group por predefinição, pelo que parar uma unidade envia um sinal para todos os processos que estão dentro dela. O PID principal é mais específico. É o único processo cuja saída termina a unidade e cujo código de saída se torna o resultado da unidade. A confusão começa quando o cgroup é interpretado como se fosse o PID principal.
Type=simple indica o arranque antes da execução do binário
Type=simple é o valor predefinido quando ExecStart= está definido e Type= e BusName= não estão presentes. O systemd cria o processo, considera a unidade iniciada imediatamente e trata esse processo como o PID principal. As unidades subsequentes começam de imediato, antes de o binário do serviço sequer ser executado.
Esse último detalhe explica uma surpresa comum. Um erro de digitação no caminho ExecStart= ainda produz uma tarefa de arranque bem-sucedida, e a falha surge um momento depois, quando a execução falha. O systemd regista esse caso com o código de saída 203, que a sua própria tabela designa como EXEC e define como uma falha ao executar o binário do serviço. Portanto, systemctl start terminar sem erro não prova que o seu binário exista.
Use simple para um programa que permanece em primeiro plano e nunca passa para segundo plano por iniciativa própria. Isso abrange a maioria dos daemons modernos e praticamente tudo o que escrever por conta própria.
Type=exec aguarda o início efetivo do programa
Type=exec é simple com mais uma etapa. O systemd considera a unidade iniciada apenas depois de o fork e a execução do binário terem sido concluídos com êxito. Um binário em falta ou um User= que não possa ser resolvido falha agora o próprio job de arranque, em vez de comunicar sucesso e falhar silenciosamente um momento depois.
Type=exec foi introduzido no systemd 240, por isso está disponível em todas as distribuições de servidor atuais. O Ubuntu 24.04 inclui o systemd 255 e o Debian 13 inclui o systemd 257, em agosto de 2026. Verifique a sua versão com systemctl --version.
O custo é uma etapa adicional de sincronização durante o arranque. O benefício é um estado de saída correto de systemctl start. Para um programa em primeiro plano, prefira exec a simple.
Type=forking e como o PID principal é perdido
Type=forking informa ao systemd que o processo em ExecStart= vai criar um processo filho e depois terminar de propósito. O systemd espera que esse primeiro processo termine e só depois considera a unidade iniciada. O processo filho que fica em execução é o daemon. Esse comportamento vem da era do SysV, quando nenhum componente supervisionava um daemon depois de o script de init terminar e um ficheiro PID era o único registo do que estava em execução. Essa limitação está no centro de por que o systemd substituiu os scripts de init.
A dificuldade está na identificação. O processo iniciado pelo systemd já terminou, por isso o systemd precisa de determinar qual processo sobrevivente é o principal. Defina PIDFile= como o ficheiro escrito pelo daemon, normalmente um caminho sob /run, e o systemd lê o PID desse ficheiro. O systemd também verifica se o PID indicado nesse ficheiro corresponde a um processo que já pertence a este serviço. Assim, um ficheiro obsoleto que indique um processo não relacionado é rejeitado, em vez de ser considerado válido.
Sem PIDFile=, aplica-se GuessMainPID=, cujo valor predefinido é yes. A inferência só é fiável quando o serviço fica reduzido a um único processo. O manual declara claramente a limitação: se o daemon for composto por mais de um processo, a inferência pode estar errada e a deteção de falhas deixa de funcionar. Uma unidade também pode acabar com um PID principal igual a 0. Nesse caso, o systemd não tem nenhum processo para supervisionar.
A maioria dos daemons que criam processos filhos também tem uma opção para permanecer em primeiro plano. Use essa opção com Type=exec e elimine a linha PIDFile=. Menos componentes significam menos formas de perder o PID.
Type=oneshot para trabalho que termina
Type=oneshot espera que o processo seja executado e termine. O systemd considera a unidade iniciada apenas depois de o processo terminar, o que torna oneshot o formato correto para qualquer tarefa que outra unidade tenha de aguardar. Este também é o valor padrão implícito quando uma unidade não especifica Type= nem ExecStart=.
Dois comportamentos são específicos de oneshot. Este é o único tipo que aceita mais de uma linha ExecStart=, e essas linhas são executadas pela ordem. O tempo limite de arranque também fica desativado por padrão. Por isso, um oneshot que bloqueie aguarda indefinidamente, a menos que defina TimeoutStartSec= manualmente.
Depois de o processo terminar, a unidade volta ao estado inativo. RemainAfterExit=yes mantém-na active sem nenhum processo em execução. Esta é a versão deliberada do sintoma apresentado no início desta página. Está correta quando a tarefa da unidade é deixar um estado configurado, em vez de manter algo em execução: carregar um conjunto de regras de firewall ou iniciar uma stack de contentores. Este é o padrão usado por uma stack do Docker Compose que regressa depois de um reboot, em que a unidade executa o comando compose, termina e permanece ativa porque os contentores que iniciou continuam a existir depois dela. Uma unidade oneshot também é o que um agendamento aciona, sendo essa a outra metade de executar uma tarefa num systemd timer em vez de cron.
Type=notify permite que o serviço indique quando está pronto
Type=notify transfere a decisão para o serviço. O systemd mantém o trabalho de arranque aberto até o processo enviar READY=1 através de um socket Unix, cujo caminho recebe na variável de ambiente NOTIFY_SOCKET. A interface C é sd_notify(3), e muitos servidores já a suportam.
Esta é a resposta correta à pergunta "está iniciado". simple e exec comunicam que o serviço foi iniciado antes de este ler a configuração ou abrir o socket de escuta. Assim, uma unidade dependente pode arrancar demasiado cedo e falhar a primeira ligação. notify comunica que o serviço foi iniciado no momento em que o próprio serviço indica estar pronto.
O systemd aceita essa mensagem apenas do processo principal. É isso que NotifyAccess=main significa, e Type=notify implica esse comportamento. Se a mensagem vier de um processo filho ou auxiliar, defina NotifyAccess=all. Um script de shell pode chamar systemd-notify --ready, mas isso é executado como um processo separado e de curta duração. Por isso, precisa de NotifyAccess=all, e o systemd pode não conseguir atribuir uma mensagem cujo emissor já terminou. Um serviço que implemente o protocolo diretamente é mais fiável.
Vale a pena conhecer mais duas definições relacionadas. Type=notify-reload, disponível desde o systemd 253, estende o mesmo handshake aos reloads. Assim, systemctl reload só devolve o controlo quando o serviço comunica que o reload terminou, em vez de devolver o controlo quando o sinal é enviado. WatchdogSec= pede a um serviço que envia notificações para enviar uma mensagem de keep-alive num intervalo definido. O systemd trata um prazo não cumprido como uma falha.
Type=dbus e Type=idle
Type=dbus aguarda até que o serviço obtenha um nome no D-Bus, o barramento de mensagens usado pelos serviços do sistema e do ambiente de trabalho para comunicarem entre si. Requer BusName= e torna-se o padrão assim que BusName= é definido. Use-o apenas num serviço que realmente registe um nome no barramento.
Type=idle comporta-se como simple, mas adia a execução do programa até que os trabalhos em fila sejam enviados, com um limite de cinco segundos. Existe para impedir que a saída da consola durante o arranque se misture com as mensagens de estado. Não é uma ferramenta de ordenação e não deve ser usado num serviço normal.
Por que um script wrapper deixa o systemd associado ao PID errado
Este é o formato que produz o sintoma original.
[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 9101O systemd regista o shell como PID principal. O shell permanece ativo enquanto exporter é executado em primeiro plano. Se server terminar, o shell não percebe, portanto o PID principal continua ativo, a unidade continua active e Restart= não tem nada sobre o que atuar. Ambos os processos permanecem no cgroup da unidade durante todo o tempo, portanto systemctl stop continua a fazer a limpeza corretamente. O que falhou foi a supervisão, não a limpeza.
A correção depende de quantos processos de longa duração a unidade realmente tem.
Se houver um, substitua o shell por ele.
#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yamlexec substitui o shell pelo programa indicado e mantém o mesmo PID, portanto o PID que o systemd registou passa a pertencer ao daemon. Melhor ainda, remova o wrapper. Environment= e EnvironmentFile= transportam as variáveis, e ExecStartPre= executa a etapa de configuração, portanto o systemd pode iniciar o daemon diretamente e conhecer o respetivo PID desde o início.
Se houver dois, nenhum PID único representa a unidade. Divida-os em duas unidades e ordene-as com After= e Wants=. Uma unidade por processo é a organização que o systemd supervisiona melhor, e é a única forma de cada processo ter o seu próprio comportamento de reinício.
O que ExitType=cgroup altera
ExitType= foi adicionado ao systemd 250. O valor predefinido é main: a unidade é considerada parada quando o processo principal termina. Com ExitType=cgroup, a unidade é considerada em execução enquanto existir qualquer processo no respetivo cgroup.
[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcherIsto resolve um problema específico. Um launcher que inicia o trabalho efetivo e depois termina faria, com ExitType=main, que o systemd considerasse a unidade parada e terminasse os processos restantes. Com ExitType=cgroup, a unidade acompanha o grupo inteiro.
É importante perceber o que isto não resolve. ExitType=cgroup mantém uma unidade ativa enquanto existir pelo menos um processo, por isso uma unidade que contenha dois daemons continua ativa depois de um deles terminar. Isto corrige o caso do launcher. Não transforma uma unidade num supervisor de vários processos independentes. ExitType= também não pode ser combinado com Type=oneshot.
O cgroup também é o destino da contabilização de recursos, por isso limites como MemoryMax= e CPUQuota= aplicam-se a todos os processos iniciados pela unidade, independentemente do que Type= indicar sobre o PID principal. Essa parte é abordada em limitar a memória e a CPU de um serviço com systemd.
Como descobrir qual processo o systemd está realmente a monitorizar
Execute estes passos, pela ordem indicada, na unidade que está a depurar. Primeiro, leia o que o systemd carregou. Depois, leia o que ele monitoriza. Por fim, compare esses dados com a tabela de processos.
systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.servicesystemctl cat apresenta o ficheiro da unidade juntamente com todos os drop-ins aplicáveis, para que veja o que o systemd carregou, e não o ficheiro que se recorda de ter editado. systemctl show apresenta os valores efetivos, incluindo as predefinições que não especificou. Registe o valor de MainPID antes de continuar.
systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"systemd-cgls apresenta todos os processos no cgroup da unidade. A linha ps descreve o único processo supervisionado pelo systemd. Leia as duas informações em conjunto. Um MainPID igual a 0 significa que o systemd não tem nenhum processo para monitorizar. Um MainPID que resolva para uma shell, enquanto o cgroup também contém o seu daemon, corresponde ao caso de wrapper descrito acima. Um cgroup com mais processos do que esperava indica a presença de um launcher ou de um daemon que cria processos filhos.
systemctl status app.service
journalctl -u app.service -bsystemctl status apresenta a linha de estado e a árvore do cgroup em conjunto, respondendo frequentemente às duas perguntas de uma só vez. journalctl -u limitado ao boot atual com -b apresenta os eventos de arranque e paragem registados pelo systemd para a unidade, juntamente com os códigos de saída observados. Se o daemon escrever no seu próprio ficheiro de log em vez de usar o journal, leia também esse ficheiro, porque o systemd só consegue registar o que lhe chega.
Quando alterar Type=, faça reload e restart.
systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.servicesystemd-analyze verify analisa o ficheiro e comunica as definições que não consegue aceitar. daemon-reload faz com que o systemd volte a ler os ficheiros das unidades a partir do disco. Uma alteração a Type= não se aplica a uma unidade que já esteja em execução. Por isso, o restart é obrigatório.
Teste a alteração. Obtenha, a partir de systemd-cgls, o PID do processo que realmente pretende verificar e termine-o. Execute systemctl is-active app.service imediatamente a seguir. Se Type= estiver correto, a unidade sai do estado ativo. Se continuar ativa, o systemd ainda está a monitorizar outro processo.
Qual Type= do serviço systemd deve utilizar
- Um programa que permanece em primeiro plano:
Type=exec. - Um programa que suporta notificações de prontidão:
Type=notify, enotify-reloadse também confirmar recarregamentos. - Um daemon que exige passar para segundo plano:
Type=forkingcomPIDFile=, ou a opção para permanecer em primeiro plano comType=exec. - Um script que executa uma tarefa e termina:
Type=oneshot, além deRemainAfterExit=yesquando o objetivo era deixar o estado persistido. - Um launcher que termina enquanto os processos filhos continuam em execução:
Type=simplecomExitType=cgroup.
Se não souber qual destes tipos um daemon de terceiros necessita, leia primeiro o ficheiro de unidade fornecido pelo pacote. Executar systemctl cat numa unidade fornecida pela distribuição mostra o Type= escolhido pelo projeto upstream, e essa escolha foi testada por mais pessoas do que a sua.
FAQ
Por que a minha unidade systemd continua ativa quando o processo morreu?
Porque o processo que o systemd trata como principal ainda está ativo. O systemd monitoriza um único PID por serviço, escolhido de acordo com Type=, em vez de monitorizar todos os processos no cgroup da unidade. Um script wrapper iniciado com Type=simple é a causa mais comum: o shell é o PID principal, por isso a unidade continua ativa quando termina um daemon que o shell iniciou em segundo plano. Execute systemctl show -p MainPID app.service, liste o cgroup da unidade com systemd-cgls --unit=app.service e compare os dois.
Qual é a diferença entre Type=simple e Type=exec?
Type=simple considera que a unidade foi iniciada assim que o systemd criou o processo, antes de o binário ser executado. Por isso, um caminho incorreto em ExecStart= ainda produz uma tarefa de arranque bem-sucedida, seguida de uma falha. Type=exec espera até que a execução seja bem-sucedida, por isso essa falha é comunicada pela própria tarefa de arranque. Ambos tratam o mesmo processo como o PID principal. Type=exec requer o systemd 240 ou mais recente.
Ainda preciso de PIDFile= com Type=forking?
Sim, sempre que o daemon escrever um. Sem essa opção, o systemd recorre a GuessMainPID=, que é uma estimativa e só é fiável para um serviço que permaneça num único processo. Quando a estimativa está errada ou não é possível fazê-la, a deteção de falhas e o reinício automático deixam de funcionar para essa unidade. Aponte PIDFile= para o caminho exato onde o daemon escreve o ficheiro, normalmente em /run.
Quando devo usar RemainAfterExit=yes?
Quando o objetivo da unidade era alterar o estado do sistema, e não manter um processo em execução. Uma unidade Type=oneshot que carrega regras de firewall ou inicia uma stack de contentores termina assim que conclui o trabalho. Sem RemainAfterExit=yes, a unidade fica inativa, deixando systemctl stop sem nada para parar e sem forma de executar uma limpeza ExecStop=. Com essa opção, a unidade permanece ativa sem processos, o que é pretendido neste caso.
Alterar Type= requer um daemon-reload?
Sim, e também é necessário reiniciar a unidade. systemctl daemon-reload faz o systemd reler os ficheiros de unidade no disco, mas uma instância em execução mantém o Type= com que foi iniciada. Execute sudo systemctl daemon-reload e depois sudo systemctl restart app.service antes de testar. Caso contrário, continuará a observar o comportamento de supervisão antigo.