Por que o systemd não reinicia seu serviço
Restart= monitora apenas o processo principal: um filho morto no mesmo cgroup fica invisível. Entenda Type=, limites de reinício e o journal.
A resposta curta: as políticas de reinício do systemd monitoram um processo
As políticas de reinício do systemd monitoram um processo por unidade: o processo principal. Restart= lê o estado de saída desse processo e nada mais. O grupo de controlo de uma unidade pode conter vinte processos. Um deles pode terminar, mas a unidade permanece active (running) porque o processo principal continua ativo. Para o systemd, não ocorreu nenhuma falha. Por isso, nada é reiniciado.
O systemd conhece os outros processos. Termina-os quando a unidade para, contabiliza a memória deles nos limites da unidade, aplica-lhes a quota de CPU da unidade e apresenta-os em systemctl status. No entanto, nunca lê o estado de saída desses processos. A lógica de reinício e o cgroup são duas coisas diferentes. A maior parte deste guia trata precisamente da diferença entre ambos.
O que o cgroup contém e o que a lógica de reinício consulta
Um cgroup (control group) é um objeto do kernel que contém um conjunto de processos. Cada unidade de serviço recebe um cgroup, com o nome da unidade. Um processo não pode sair dele. Os processos filhos herdam o cgroup do processo pai, e um processo sem privilégios não pode mover-se para outro cgroup. É por isso que o systemd consegue limpar um daemon que faz fork duas vezes, algo que os scripts init antigos nunca conseguiam fazer de forma fiável.
Veja os dois factos lado a lado:
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Restart -p RestartUSec myapp.servicesystemd-cgls lista todos os processos da unidade. MainPID é o único número que a política de reinício consulta. Quando esses dois valores não correspondem ao seu modelo mental, essa divergência é o problema. MainPID=0 é pior do que um PID incorreto: significa que o systemd não está a monitorizar nada, pelo que nenhum valor de Restart= pode ser acionado.
Existe uma exceção real à regra do processo principal. Se o kernel OOM killer terminar qualquer processo dentro do cgroup da unidade, o systemd deteta-o porque monitoriza o ficheiro memory.events do cgroup. OOMPolicy= determina o que acontece a seguir, e o valor predefinido é stop: a unidade inteira é parada, o resultado é registado como oom-kill e isso conta como uma falha, pelo que Restart=on-failure é acionado. O journal indica isso claramente.
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Failed with result 'oom-kill'.Assim, um processo filho terminado por falta de memória derruba a unidade, enquanto o mesmo processo filho terminado por uma falha de segmentação não o faz. Se definir limites de memória numa unidade, leia como MemoryMax e CPUQuota se aplicam ao cgroup de uma unidade antes de ajustar a política de reinício, porque estas duas funcionalidades interagem aqui e em nenhum outro ponto.
Como Type= escolhe o processo principal
Type= na secção [Service] não trata apenas da ordem de arranque. É a regra que determina qual PID (ID do processo) passa a ser MainPID, o que equivale a determinar o que Restart= consegue ver.
Type=simpleé o valor predefinido. O processo que systemd bifurca a partir deExecStart=é o processo principal. systemd marca a unidade como iniciada imediatamente, antes de saber seexecchegou sequer a funcionar. Um erro de digitação no caminho do binário produz uma tarefa de arranque bem-sucedida e, depois,Main process exited, code=exited, status=203/EXECum momento mais tarde.Type=execcomporta-se comosimple, mas a tarefa de arranque aguarda até queexectenha sido concluído com sucesso. Assim, o erro de digitação anterior passa a causar uma falha de arranque real. Requer systemd 240 ou mais recente, uma versão disponível em todas as distribuições suportadas. Prefira-o asimple.Type=forkingespera que o processo deExecStart=bifurque um daemon em segundo plano e termine depois. systemd aguarda que o processo pai termine e, em seguida, procura o daemon real. Forneça-lhePIDFile=. Sem um destes,GuessMainPID=(ativado por predefinição) só funciona quando resta exatamente um processo no cgroup. Se deixar dois processos,MainPIDpermanece0.Type=notifysignifica que o serviço chamasd_notify(3)e enviaREADY=1quando já consegue receber tráfego. Também pode enviarMAINPID=para fornecer a systemd outro processo para monitorizar.NotifyAccess=tem o valor predefinidomain, pelo que uma notificação enviada por um processo filho é ignorada e o journal identifica o PID de onde veio.Type=oneshotnão tem um processo principal persistente. A unidade fica inativa assim queExecStart=termina, a menos que definaRemainAfterExit=yes.Restart=alwayseRestart=on-successsão recusados neste caso, com a mensagemService has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing.. Os outros valores, incluindoon-failure, são aceites.
Vale a pena memorizar dois erros de Type=forking, porque cada um deixa uma unidade com um aspeto avariado sem uma causa visível:
myapp.service: Can't open PID file /run/myapp.pid (yet?) after start: No such file or directory
myapp.service: New main PID 4711 does not belong to service, and PID file is not owned by root. Refusing.O primeiro significa que o daemon escreve o seu ficheiro PID noutro local ou que o escreve depois de systemd o procurar. O segundo significa que o ficheiro PID identifica um processo fora do cgroup da unidade, que systemd se recusa a adotar. Caso contrário, um ficheiro PID com permissões de escrita poderia ser usado para fazer systemd enviar sinais a qualquer processo no servidor.
Por que um script wrapper oculta a morte dos seus processos filhos
Este é o formato que origina a pergunta do título.
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
waitA unidade é Type=simple, portanto o processo principal é a shell. wait sem argumentos só retorna depois de todos os processos filhos terminarem. Mate o worker e a shell continua à espera do processo web. Por isso, a shell não termina, MainPID não termina e Restart= nunca é consultado. O cgroup passa a conter menos um processo, systemctl status mostra a árvore mais curta e a unidade continua em active (running). O systemd não monitoriza essa árvore em busca de alterações.
Uma segunda versão do mesmo erro é mais silenciosa:
ExecStart=/bin/sh -c 'export APP_ENV=production; /usr/local/bin/myapp'O processo principal é a shell, não myapp. Em systemctl stop, o systemd envia SIGTERM ao processo principal, mas uma shell à espera de um processo filho em primeiro plano não encaminha o sinal. A paragem demora então todo o TimeoutStopSec, que é de 90 segundos por predefinição, e termina assim:
myapp.service: State 'stop-sigterm' timed out. Killing.
myapp.service: Killing process 4711 (myapp) with signal SIGKILL.A correção é exec. Escreva exec /usr/local/bin/myapp e a shell será substituída pelo programa. Assim, MainPID será o programa e os sinais chegarão até ele. Melhor ainda, elimine a shell e use Environment= ou EnvironmentFile= na unidade. Tenha em atenção que este erro fica oculto quando a cadeia -c contém um único comando, porque bash e dash otimizam ambos esse caso para um exec direto. Adicione um segundo comando à cadeia e a shell continuará ativa à frente do seu programa.
Reproduza o problema numa VPS de teste em dois minutos
Guarde o wrapper acima como /usr/local/bin/two-children.sh, torne-o executável com chmod +x e substitua os dois caminhos dos programas por sleep 3600. Aponte uma unidade para ele com Type=simple e Restart=on-failure. Em seguida, execute systemctl daemon-reload e inicie-a. Execute systemd-cgls --unit two-children.service e observe os três PIDs: a shell e os seus dois processos filhos. Mate um dos processos filhos com sudo kill <pid>. Verifique novamente a unidade. A árvore terá menos um processo, o estado continuará em active (running) e o journal não terá nada de novo para indicar. Agora execute sudo kill -9 <shell pid>. A unidade falha, o processo filho restante é terminado porque KillMode=control-group é o valor predefinido e o journal mostra Scheduled restart job, restart counter is at 1.
Vocabulário completo de Restart= e quando on-failure é melhor do que always
Restart= aceita um de sete valores. A distinção entre eles é o que conta como saída normal. O systemd considera o código de saída 0, qualquer código listado em SuccessExitStatus= e os sinais SIGHUP, SIGINT, SIGTERM e SIGPIPE como uma saída normal. Todo o resto, incluindo SIGKILL e SIGSEGV, é uma saída anormal.
noé o padrão. A unidade nunca reinicia sozinha. Por isso, uma unidade sem uma linhaRestart=termina na primeira falha e permanece parada.on-successreinicia apenas depois de uma saída normal.on-failurereinicia depois de um código de saída diferente de zero, de um sinal anormal, de um tempo limite de arranque ou paragem ou da expiração do watchdog.on-abnormalreinicia depois de um sinal anormal, de um tempo limite ou da expiração do watchdog, mas nunca depois de um código de saída simples diferente de zero.on-abortreinicia apenas depois de um sinal anormal, ou seja, depois de uma falha.on-watchdogreinicia apenas quandoWatchdogSec=expira.alwaysreinicia depois de todos os casos anteriores, incluindo uma saída normal com o estado 0.
on-failure é o valor padrão correto para um daemon de execução contínua. Recupera de uma falha e deixa um exit 0 deliberado sem alterações. always é adequado para um programa que termina normalmente por razões fora do seu controlo, como um cliente de túnel que devolve 0 quando o extremo remoto se desliga. O custo de always é ocultar erros: um serviço que arranca, lê um ficheiro de configuração inválido, regista o erro e termina com o código 0 ficará num ciclo infinito. O único sinal será o contador de reinícios a aumentar.
SuccessExitStatus= altera a distinção entre saída normal e anormal. O Borg termina com 1 para avisos e com 2 para erros. Por isso, uma unidade de backup sem SuccessExitStatus=1 é marcada como falhada sempre que ignora um ficheiro ilegível. RestartPreventExitStatus= lista os códigos que impedem um reinício mesmo com always. É a forma correta de um programa indicar que não deve voltar a arrancar. RestartForceExitStatus= faz o contrário. Um trabalho de backup deve estar numa unidade Type=oneshot acionada por um temporizador, e não num ciclo de reinícios. o par de serviço e temporizador que executa um trabalho segundo um agendamento mostra a estrutura a copiar nesse caso.
Há um aviso sobre os testes. Terminar o serviço com um kill <pid> simples envia SIGTERM, que está na lista de saídas normais. Por isso, Restart=on-failure não faz nada corretamente, e pode concluir que a configuração está errada. Use kill -9 <pid> ou systemctl kill -s SIGKILL myapp.service. Lembre-se também de que nenhum valor de Restart= é acionado depois de systemctl stop, nem quando a unidade foi parada porque uma dependência BindsTo= ou PartOf= deixou de estar disponível. Um trabalho de paragem não é uma falha.
RestartSec e o padrão de 100 milissegundos
RestartSec= é a pausa entre a paragem da unidade e o reinício feito pelo systemd. O padrão é 100 milissegundos. Verifique o valor efetivamente carregado pela unidade:
systemctl show -p RestartUSec -p StartLimitIntervalUSec -p StartLimitBurst myapp.serviceUma unidade que não tenha definido esse valor apresenta RestartUSec=100ms. Esse padrão é adequado para um serviço que falha uma vez e volta a funcionar. É inadequado para um serviço que não consegue iniciar, porque cinco reinícios ocorrem em menos de meio segundo. É exatamente isso que aciona o limite de taxa descrito a seguir. Para qualquer serviço que dependa de uma base de dados, de uma montagem ou de uma rota de rede, defina RestartSec=5s ou um valor superior.
Desde agosto de 2026, o systemd 254 e versões posteriores também oferecem RestartSteps= e RestartMaxDelaySec=. Essas opções aumentam o atraso de RestartSec= até um limite máximo ao longo do número de tentativas especificado. O Ubuntu 24.04 inclui o systemd 255 e disponibiliza essas opções. O Debian 12 inclui o systemd 252 e não as disponibiliza. Aumentar progressivamente os atrasos é a opção correta quando a dependência pode ficar indisponível durante muito tempo.
O que "start request repeated too quickly" realmente significa
Este é o estado que leva os leitores a pensar que o systemd desistiu de forma arbitrária. Trata-se de um contador. A regra é a seguinte: se uma unidade for iniciada mais de StartLimitBurst= vezes dentro de StartLimitIntervalSec=, o systemd recusa iniciá-la novamente e coloca-a no estado failed. Os valores predefinidos são 5 arranques em 10 segundos.
O journal mostra a sequência:
myapp.service: Scheduled restart job, restart counter is at 5.
myapp.service: Start request repeated too quickly.
myapp.service: Failed with result 'start-limit-hit'.
Failed to start myapp.service - My application.e systemctl start apresenta a correção já escrita:
Job for myapp.service failed because start of the service was attempted too often. See "systemctl status myapp.service" and "journalctl -xeu myapp.service" for details. To force a start use "systemctl reset-failed myapp.service" followed by "systemctl start myapp.service" again.systemctl reset-failed myapp.service limpa o contador e o estado failed. Nenhum outro comando faz isso, por isso um systemctl start simples continua a ser recusado até executar systemctl reset-failed myapp.service. Os arranques manuais também contam para o limite. Assim, algumas execuções impacientes de systemctl restart enquanto edita um ficheiro de configuração podem acioná-lo sem que tenha ocorrido qualquer falha do processo.
A parte que induz em erro: start-limit-hit nunca indica por que motivo o serviço estava a falhar. Apenas informa que falhou repetidamente e num curto intervalo. O motivo real está nas linhas anteriores do journal.
Ambas as definições pertencem à secção [Unit]. Encontrará exemplos que as colocam em [Service], uma localização aceite por versões antigas do systemd. É aí que começa a confusão. Escreva-as em [Unit] e depois peça ao systemd para mostrar o que carregou com systemctl show, porque apenas o valor carregado é considerado.
[Unit]
Description=My application
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=exec
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10sIsto dá à unidade cinco tentativas num intervalo de cinco minutos antes de desistir. StartLimitIntervalSec=0 desativa completamente o limite. Deve saber o que está a escolher: um serviço que nunca consegue iniciar passará a tentar novamente para sempre e escreverá uma entrada no journal a cada tentativa. Os valores predefinidos para todo o sistema estão em /etc/systemd/system.conf, como DefaultStartLimitIntervalSec= e DefaultStartLimitBurst=.
Há uma definição relacionada que exige atenção. StartLimitAction= decide o que acontece quando o limite é atingido e aceita valores como reboot, reboot-force e poweroff. O valor predefinido é none, que coloca a unidade no estado failed e não altera o estado da máquina. Num VPS remoto, poweroff significa que a máquina permanece desligada até abrir a consola do fornecedor.
Corrija assim: um processo por unidade
Esta é a solução na maioria dos casos. Se dois programas tiverem de ser executados, escreva duas unidades. Cada uma terá um processo principal real, um estado de saída real e a sua própria política de reinício. Também terá logs separados, limites de recursos separados e contadores de reinício separados, que é o comportamento pretendido durante a madrugada.
Expresse a relação entre as unidades nos ficheiros das unidades, não num script de shell.
After=ordena apenas o arranque. Não diz nada sobre falhas.Requires=inicia a outra unidade em paralelo com esta e para esta unidade se a outra for parada explicitamente.BindsTo=éRequires=mais o comportamento necessário neste caso: esta unidade para quando a outra para por qualquer motivo, incluindo uma falha. Combine-o comAfter=, caso contrário a ordem fica indefinida.PartOf=propaga a paragem e o reinício para as unidades descendentes, por issosystemctl restart myapp.targetalcança todas as unidades que sãoPartOf=dela.Upholds=(systemd 249 e versões posteriores, portanto Ubuntu 22.04 e posteriores) mantém a unidade indicada em execução: se ela parar, systemd inicia-a novamente. Está sujeita ao mesmo limite de taxa de arranque que todas as outras unidades.
Um worker que nunca deve ser executado sem o seu servidor de API e que systemd mantém ativo sempre que a API está disponível:
# /etc/systemd/system/myapp-api.service
[Unit]
Description=myapp API server
Wants=network-online.target
After=network-online.target
Upholds=myapp-worker.service
[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp serve
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target# /etc/systemd/system/myapp-worker.service
[Unit]
Description=myapp background worker
BindsTo=myapp-api.service
After=myapp-api.service
StartLimitIntervalSec=120
StartLimitBurst=5
[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp worker
Restart=on-failure
RestartSec=5sO worker não tem uma secção [Install] e nunca é ativado manualmente. A unidade da API inclui-o com Upholds=, por isso systemctl enable --now myapp-api.service é o único comando que precisa de executar. Recarregue a configuração e verifique o resultado criado por systemd para o par:
sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/myapp-worker.service
systemctl list-dependencies myapp-api.servicesystemd-analyze verify não imprime nada quando o ficheiro está correto. Qualquer saída indica um problema, normalmente uma chave que systemd não reconhece na secção onde foi escrita ou uma dependência de uma unidade que não existe.
Correção dois: Type=notify, para o systemd conhecer mais do que um PID
Se o programa utiliza o protocolo de notificações do systemd, use-o. Com Type=notify, o serviço informa ao systemd quando está pronto, o que torna a ordenação real em vez de baseada em expectativas. Também pode enviar MAINPID= para indicar ao systemd o processo relevante, em vez de um launcher.
WatchdogSec= é a parte que justifica o esforço. Defina-o, e o serviço terá de enviar WATCHDOG=1 através de sd_notify(3) pelo menos com essa frequência. Quando as mensagens param, o systemd termina o serviço com SIGABRT e marca-o como falhado. Assim, Restart=on-failure ou Restart=on-watchdog volta a iniciá-lo. Esta é a única forma integrada de reiniciar um processo que continua ativo, mas está bloqueado. Nenhuma política baseada no código de saída consegue detetar essa situação.
[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/local/bin/myapp serve
WatchdogSec=30s
Restart=on-failure
RestartSec=5sUma ativação do watchdog aparece no journal como myapp.service: Watchdog timeout (limit 30s)!, seguida da terminação do processo. Se, em vez disso, a unidade permanecer em activating (start) até TimeoutStartSec expirar, READY=1 nunca chegou. O programa pode não utilizar o protocolo, ou NotifyAccess=main pode estar a rejeitar uma notificação enviada por um processo filho. O journal regista essa situação com os dois PIDs.
Para software que disponibiliza um endpoint HTTP de verificação de estado, mas não tem suporte para sd_notify, as opções corretas são uma pequena unidade de timer que consulta o endpoint e chama systemctl restart, ou deixar o runtime de contentores fazer a verificação. É para isso que existem as verificações de estado do Compose e o respetivo comportamento de reinício.
Correção três: um supervisor dentro da unidade, apenas quando não houver alternativa
Alguns softwares são realmente distribuídos como um conjunto de processos iniciado por um launcher que não pode ser dividido. Nesse caso, execute um supervisor dentro da unidade e aceite a consequência: o systemd monitoriza o supervisor, o supervisor monitoriza todo o resto e a política de reinício passa a estar definida em dois ficheiros.
A forma mais comum deste padrão é um runtime de contentores. Uma unidade docker compose ou podman segue exatamente este padrão, com a política de reinício de cada contentor definida no ficheiro Compose e a unidade systemd apenas a manter o runtime em execução. Se essa for a sua situação, a unidade que inicia uma stack Compose no boot apresenta a versão funcional, incluindo o motivo pelo qual Type=oneshot com RemainAfterExit=yes é normalmente a opção correta nesse caso.
O cgroup continua a funcionar a seu favor. Tudo o que o supervisor iniciar permanece no cgroup da unidade, portanto MemoryMax=, CPUQuota= e a limpeza no momento da paragem continuam a abranger toda a árvore de processos. Apenas a decisão de reinício é delegada.
Independentemente do supervisor escolhido, não defina Restart=always na unidade externa e uma política de reinício agressiva dentro dela sem analisar as consequências. Duas camadas de lógica de reinício, cada uma com o seu próprio backoff, produzem um serviço que entra em flapping durante minutos e um journal que não explica o motivo.
ExitType=cgroup não significa "reiniciar quando qualquer processo terminar"
ExitType= (systemd 250 e mais recentes, portanto Ubuntu 24.04 e Debian 12 já o incluem) é a configuração que aparece quando se pesquisa este problema, mas faz o oposto do que o nome sugere. O valor predefinido, ExitType=main, significa que o serviço é considerado parado quando o processo principal termina. ExitType=cgroup significa que o serviço é considerado em execução até o último processo do cgroup terminar.
Por isso, ExitType=cgroup torna uma unidade menos sensível à terminação de um processo, e não mais. É a configuração correta para um programa que cria o seu processo de trabalho real e termina o processo-pai sem escrever um ficheiro PID, situação em que Type=forking não consegue localizar o daemon. É a configuração errada para a falha descrita aqui.
Não existe um valor Restart= com o significado de "reiniciar a unidade quando qualquer processo do cgroup terminar". Se precisar desse comportamento, terá de usar um processo por unidade. Se não puder dividir o programa e controlar o script wrapper, a alternativa mais próxima é wait -n, que termina assim que o primeiro processo filho sai:
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait -n
exit 1A terminação de qualquer processo filho faz agora o wrapper terminar com um estado diferente de zero, pelo que Restart=on-failure atua. Esta é uma solução de compromisso, não uma correção. Continua a ter um único contador de reinícios para dois programas, um único fluxo de logs e nenhuma forma de reiniciar apenas a parte que falhou.
Como inspecionar o que realmente aconteceu
Quatro comandos, nesta ordem.
systemctl status myapp.service
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Result -p ExecMainStatus myapp.service
journalctl -u myapp.service -b -o short-precisesystemctl status mostra o estado, o PID principal e a árvore de cgroups num único ecrã. Uma unidade saudável apresenta Active: active (running), com uma linha Main PID: que identifica o processo esperado. Se a árvore no fim listar processos que não reconhece, ou não incluir um processo que reconhece, já tem a resposta.
systemd-cgls --unit apresenta a mesma árvore sem truncamento. Isto torna-se importante quando uma unidade mantém mais do que alguns processos.
systemctl show apresenta factos num formato legível por máquinas. NRestarts= é o contador de reinícios. É a forma mais rápida de distinguir um serviço que reiniciou quarenta vezes de outro que está ativo desde o boot. Result= contém o último motivo da falha: exit-code, signal, timeout, oom-kill, watchdog ou start-limit-hit. ExecMainStatus= é o estado de saída bruto do último processo principal.
O journal contém a sequência dos eventos. Estas são as três linhas que deve procurar:
myapp.service: Main process exited, code=exited, status=1/FAILURE
myapp.service: Failed with result 'exit-code'.
myapp.service: Scheduled restart job, restart counter is at 1.code=exited, status=N significa que o programa escolheu devolver N. A falha está no programa ou na configuração. code=killed, signal=SEGV significa que o programa terminou de forma anormal. code=killed, signal=TERM normalmente significa que outro componente pediu a sua paragem. Isto não é uma falha e não ativa Restart=on-failure. code=dumped significa que o programa deixou um ficheiro core. coredumpctl list mostra esse ficheiro quando systemd-coredump está instalado.
Em mais do que uma máquina, NRestarts é o valor que deve recolher regularmente. Uma unidade cujo contador aumenta todos os dias está a falhar todos os dias, mesmo que ninguém tenha reparado. Quando já administra duas ou três máquinas, uma forma consistente de executar um comando em todos os servidores transforma essa suposição num relatório.
FAQ
Por que o systemctl diz que o meu serviço está ativo quando o processo terminou?
O systemd acompanha um processo por unidade de serviço, o processo principal, e Restart= lê apenas o estado de saída desse processo. Todos os outros processos iniciados pela unidade ficam no mesmo cgroup, e o systemd termina esses processos quando a unidade para, mas nunca os acompanha para detetar a saída. Execute systemctl show -p MainPID myapp.service e compare o número com systemd-cgls --unit myapp.service. Se o processo que terminou aparecer na árvore, mas não estiver MainPID, o systemd comportou-se exatamente como foi concebido. A correção é usar um processo por unidade, com a relação definida por BindsTo= e Upholds= entre as unidades.
O que significa "start request repeated too quickly"?
Significa que a unidade foi iniciada mais de StartLimitBurst= vezes dentro de StartLimitIntervalSec=, que por predefinição corresponde a 5 arranques em 10 segundos, e o systemd deixou de tentar. Trata-se de um limite de frequência. Ele nunca indica por que motivo o serviço estava a falhar, por isso leia as linhas anteriores do journal. Limpe o estado com systemctl reset-failed myapp.service e corrija a falha subjacente. Se o serviço depender de algo lento a arrancar, aumente RestartSec=, porque o intervalo predefinido de 100 milissegundos esgota as cinco tentativas em menos de um segundo.
Devo usar Restart=always ou Restart=on-failure?
Use on-failure na maioria dos casos. Esta opção reinicia o serviço depois de um crash, de uma saída diferente de zero, de um timeout ou de uma ativação do watchdog, mas não interfere numa exit 0 deliberada. Use always apenas quando o programa termina corretamente por motivos fora do seu controlo, como um cliente que devolve 0 quando o peer se desliga. O custo de always é que um serviço que lê uma configuração inválida, regista um erro e termina com 0 ficará num ciclo infinito, e o único sintoma visível será NRestarts a aumentar em systemctl show.
Por que terminar manualmente o meu processo não ativa um reinício?
Porque o systemd considera SIGHUP, SIGINT, SIGTERM e SIGPIPE como saídas normais, e um kill <pid> simples envia SIGTERM. Com Restart=on-failure, uma saída normal não é uma falha, por isso nada é reiniciado e a configuração parece estar avariada, embora não esteja. Teste com kill -9 <pid> ou systemctl kill -s SIGKILL myapp.service, que corresponde a uma terminação anormal e ativa efetivamente a política. A mesma regra explica por que systemctl stop nunca entra em conflito com a sua política de reinício.
Onde devem ficar StartLimitIntervalSec e StartLimitBurst?
Na secção [Unit]. Materiais mais antigos e versões mais antigas do systemd colocavam estas opções em [Service], por isso os exemplos copiados podem ser inconsistentes. Não tente adivinhar qual das opções a sua versão aceita. Depois de systemctl daemon-reload, peça ao systemd para mostrar o que carregou com systemctl show -p StartLimitBurst -p StartLimitIntervalUSec myapp.service e considere esses valores a referência. systemd-analyze verify /etc/systemd/system/myapp.service deteta chaves que o systemd não reconhece e não apresenta qualquer saída quando o ficheiro está correto.