SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-24

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 9101

O 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.yaml

exec 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/launcher

Isto 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.service

systemctl 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 -b

systemctl 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.service

systemd-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, e notify-reload se também confirmar recarregamentos.
  • Um daemon que exige passar para segundo plano: Type=forking com PIDFile=, ou a opção para permanecer em primeiro plano com Type=exec.
  • Um script que executa uma tarefa e termina: Type=oneshot, além de RemainAfterExit=yes quando o objetivo era deixar o estado persistido.
  • Um launcher que termina enquanto os processos filhos continuam em execução: Type=simple com ExitType=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.