SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor

systemd Type=: simple, forking, notify e exec

A unidade fica ativa, mas o daemon termina? Entenda simple, exec, forking, oneshot e notify, escolha o Type= correto e identifique o PID principal real.

Por que o systemd indica 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 estiver ativo. O valor de Type= na secçã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 lhe interessa 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= é aplicado quando o processo principal termina, por isso Restart=always nunca é acionado enquanto o PID principal (identificador do processo) pertencer a algo que ainda esteja em execução. Corrija primeiro Type=. A ação do systemd depois de o processo principal terminar efetivamente é uma decisão separada, abordada em o guia sobre Restart= e RestartSec=.

O que Type= decide na prática

Cada valor de Type= responde a duas perguntas ao mesmo tempo: quando o systemd pode considerar esta unidade iniciada e qual é o processo principal.

A primeira resposta controla a ordenação. Uma unidade que referencia a sua em After= espera até o systemd considerar que a sua foi iniciada. Um Type= que comunica "iniciada" demasiado cedo permite que as unidades dependentes sejam executadas antes de o seu serviço poder responder-lhes.

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. O cgroup é utilizado por systemctl stop para fazer a limpeza: KillMode= assume control-group por predefinição, pelo que a paragem de 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 estado de saída passa a ser o resultado da unidade. A confusão começa quando se interpreta o cgroup como se fosse o PID principal.

Type=simple informa que o serviço foi iniciado antes da execução do binário

Type=simple é o valor padrão 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 dependentes começam logo, 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 um trabalho de arranque bem-sucedido, 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. Na tabela do próprio systemd, o código é designado por EXEC e definido como uma falha ao executar o binário do serviço. Portanto, o retorno de systemctl start sem erro não prova que o seu binário existe.

Use simple para um programa que permanece em primeiro plano e nunca passa a executar em segundo plano por conta própria. Isto abrange a maioria dos daemons modernos e praticamente qualquer programa que escreva por si.

Type=exec aguarda o arranque 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 pouco 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. Consulte a sua versão com systemctl --version.

O custo é uma etapa adicional de sincronização no arranque. A vantagem é obter um estado de saída fiável de systemctl start. Para um programa em primeiro plano, prefira exec a simple.

Type=forking e como o PID principal se perde

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 permanece é o daemon.

A dificuldade está na identificação. O processo iniciado pelo systemd já terminou, por isso o systemd precisa determinar qual dos processos restantes é o principal. Defina PIDFile= como o ficheiro que o daemon escreve, normalmente um caminho dentro de /run, e o systemd lê o PID a partir dele. O systemd também verifica se o PID nesse ficheiro corresponde a um processo que já pertence a este serviço. Assim, um ficheiro obsoleto que identifique um processo não relacionado é rejeitado em vez de ser aceite.

Sem PIDFile=, GuessMainPID= é aplicado e o valor predefinido é yes. Esta inferência só é fiável quando o serviço fica reduzido a um único processo. O manual indica 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 ficar 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 remova a linha PIDFile=. Menos componentes significam menos formas de perder o PID.

Type=oneshot para tarefas que terminam

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 adequado para qualquer tarefa da qual outra unidade tenha de esperar. Este também é o valor predefinido implícito quando uma unidade não especifica Type= nem ExecStart=.

Dois comportamentos são específicos de oneshot. É o único tipo que aceita mais de uma linha ExecStart=, e essas linhas são executadas por ordem. O tempo limite de arranque também fica desativado por predefinição. Por isso, um oneshot que bloqueie espera indefinidamente, a menos que defina TimeoutStartSec= manualmente.

Depois de o processo terminar, a unidade regressa ao estado inactive. RemainAfterExit=yes mantém-na active sem qualquer processo em execução. Esta é a versão deliberada do sintoma descrito no início desta página. Está correta quando a tarefa da unidade era deixar um estado persistente, 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 em uma stack Docker Compose que volta a iniciar depois de um reboot, em que a unidade executa o comando compose, termina e permanece ativa porque os contentores que iniciou continuam em execução. Uma unidade oneshot também é o que uma agenda aciona, sendo essa a outra parte de executar uma tarefa num timer do systemd em vez do 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 indicam 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 na primeira ligação. notify indica que o serviço foi iniciado no momento em que o próprio serviço informa que está 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 de um 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 remetente já terminou. Um serviço que implemente o protocolo diretamente é mais fiável.

Vale a pena conhecer duas definições relacionadas. Type=notify-reload, disponível desde o systemd 253, estende o mesmo handshake aos reloads. Assim, systemctl reload só retorna quando o serviço informa que o reload terminou, em vez de retornar quando o sinal foi 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 utilizado pelos serviços do sistema e do ambiente de trabalho para comunicarem entre si. Requer BusName= e torna-se o valor predefinido assim que BusName= é definido. Utilize-o apenas num serviço que efetivamente registe um nome no barramento.

Type=idle comporta-se como simple, mas atrasa a execução do programa até que os trabalhos em fila sejam enviados, com um limite de cinco segundos. Esta opção 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 utilizada 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 o 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 qual atuar. Os dois processos permanecem no cgroup da unidade durante todo o tempo, por isso systemctl stop continua a fazer a limpeza corretamente. O problema está na supervisão, não na limpeza.

A correção depende do número real de processos de longa duração da unidade.

Se houver apenas 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= transporta a etapa de configuração, portanto o systemd pode iniciar o daemon diretamente e conhecer o respetivo PID por definição.

Se houver dois, nenhum PID individual representa a unidade. Separe-os em duas unidades e ordene-as com After= e Wants=. Uma unidade por processo é a configuraçã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 adicionada no 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 houver qualquer processo ativo no respetivo cgroup.

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

Isto resolve um problema específico. Um launcher que inicia o trabalho real e depois termina faria o systemd considerar a unidade parada e terminaria os processos restantes ao usar ExitType=main. 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, pelo que uma unidade com 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 é usado para 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. Esse aspeto é explicado em limitar a memória e a CPU de um serviço com systemd.

Como encontrar o processo que o systemd está efetivamente 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 está a monitorizar. Por fim, compare essas informações com a tabela de processos.

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

systemctl cat mostra o ficheiro da unidade juntamente com todos os drop-ins aplicáveis, para que leia o que o systemd carregou, em vez do ficheiro que se lembra de ter editado. systemctl show mostra os valores efetivos, incluindo as predefinições que não definiu explicitamente. 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 lista todos os processos no cgroup da unidade. A linha ps descreve o único processo que o systemd supervisiona. Leia as duas informações em conjunto. Um MainPID com o valor 0 significa que o systemd não tem nenhum processo para monitorizar. Um MainPID que corresponda a uma shell, enquanto o cgroup também contém o seu daemon, indica o caso de wrapper descrito acima. Um cgroup com mais processos do que esperava significa que existe um launcher ou um daemon que cria processos através de fork.

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

systemctl status mostra em conjunto a linha de estado e a árvore do cgroup, pelo que muitas vezes responde às duas perguntas de uma só vez. journalctl -u limitado ao boot atual com -b mostra os eventos de arranque e paragem que o systemd registou para a unidade, incluindo os códigos de saída observados. Se o daemon escrever no seu próprio ficheiro de log, em vez de escrever no 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 o systemd reler 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, pelo que o restart é obrigatório.

Depois, teste a alteração. Obtenha o PID do processo que realmente lhe interessa a partir de systemd-cgls e termine-o. Execute systemctl is-active app.service imediatamente a seguir. Se Type= estiver correto, a unidade sai do estado active. Se continuar active, o systemd ainda está a monitorizar outra coisa.

Que Type= do 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 ser executado em segundo plano: Type=forking com PIDFile=, ou a sua 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 iniciador que termina enquanto os seus processos filhos continuam em execução: Type=simple com ExitType=cgroup.

Se não souber qual deles um daemon de terceiros requer, leia primeiro o ficheiro de unidade fornecido com o pacote. Executar systemctl cat numa unidade fornecida pela distribuição mostra o Type= escolhido pelo projeto original. Essa escolha foi testada por mais pessoas do que a sua.

FAQ

Porque é que a minha unidade systemd continua ativa depois de o processo terminar?

Porque o processo que o systemd considera principal ainda está ativo. O systemd monitoriza um único PID por serviço, escolhido de acordo com Type=, e não todos os processos no cgroup da unidade. Um script wrapper iniciado com Type=simple é a causa habitual: 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, depois 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 a unidade iniciada assim que o systemd cria o processo, antes de o binário ser executado. Por isso, um caminho incorreto em ExecStart= ainda produz um job de arranque bem-sucedido, seguido de uma falha. Type=exec espera até a execução ter sido bem-sucedida, pelo que essa falha é comunicada pelo próprio job 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 ele, o systemd recorre a GuessMainPID=, que é uma estimativa e só é fiável para um serviço que estabiliza num único processo. Quando a estimativa está errada ou não é possível, 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 é alterar o estado do sistema, em vez de 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 é intencional 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 unidades 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.