Criar um serviço systemd próprio e executá-lo automaticamente no arranque

Publicado a 17 min de leitura

O ficheiro de unidade linha a linha: Type, User, WorkingDirectory, Restart e network-online.target. Com as mensagens de erro na forma literal e a prova de que o serviço sobrevive mesmo a um reinício.

Um programa que não regressa sozinho depois de um reinício não é um serviço num servidor, é um risco. O nohup, o screen e o tmux mantêm um processo vivo enquanto nada acontecer. Este artigo escreve um ficheiro de unidade próprio linha a linha, mostra as mensagens de erro na sua forma literal e explica como sair do carrossel de arranques quando o serviço fica lá preso.

Todos os comandos correm como root. Testado em Debian 13 (systemd 257), Debian 12 (252), Ubuntu 24.04 LTS (255) e Ubuntu 22.04 LTS (249). Sempre que os quatro sistemas divergem, fica assinalado.

O que um serviço systemd faz e o que o nohup e o screen não fazem

A diferença não está na comodidade, está na responsabilidade. O systemd arranca o processo no boot por uma ordem definida, coloca-o numa cgroup própria, recolhe o stdout e o stderr no journal, reinicia-o depois de uma falha e termina-o de forma limpa no shutdown com SIGTERM. A cgroup é aquilo cuja falta mais dói: um script iniciado com nohup deixa para trás processos filhos órfãos, uma unidade limpa o seu grupo por completo.

Preparação: programa, utilizador e diretório

Antes de a unidade existir, tem de funcionar aquilo que ela vai arrancar. Como exemplo serve um script que escreve no stdout. É precisamente esse o ponto: um serviço sob o systemd não escreve os seus próprios registos para um ficheiro, escreve no stdout, e o systemd coloca tudo no journal.

mkdir -p /opt/kh-demo
cat > /opt/kh-demo/run.sh <<'EOF'
#!/bin/bash
set -euo pipefail
while true; do
  echo "kh-demo vivo, $(date --iso-8601=seconds), PWD=$PWD, GREETING=${GREETING:-não definido}"
  sleep 10
done
EOF
chmod +x /opt/kh-demo/run.sh

A seguir, um utilizador de sistema próprio: --system atribui um UID abaixo de 1000, /usr/sbin/nologin impede sessões interativas.

useradd --system --no-create-home --home-dir /opt/kh-demo --shell /usr/sbin/nologin khdemo
id khdemo
chown -R root:khdemo /opt/kh-demo
chmod 750 /opt/kh-demo

O teste decisivo antes do ficheiro de unidade: o programa corre com este utilizador? Quem salta este passo acaba mais tarde a depurar o systemd, quando o problema está no programa.

timeout 3 runuser -u khdemo -- /opt/kh-demo/run.sh; echo "Código de saída $?"

Código de saída 124 é aqui o resultado desejado: o timeout interrompeu um programa que estava a correr. Qualquer outro valor significa que o script morreu por si próprio, e nesse caso a culpa não é do systemd.

O ficheiro de unidade linha a linha

As unidades próprias pertencem a /etc/systemd/system/ e não a /lib/systemd/system/ ou /usr/lib/systemd/system/: estes dois diretórios são do gestor de pacotes e ficam sobrescritos no próximo apt upgrade.

cat > /etc/systemd/system/kh-demo.service <<'EOF'
[Unit]
Description=KernelHost Demo Worker
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=khdemo
Group=khdemo
WorkingDirectory=/opt/kh-demo
EnvironmentFile=-/etc/kh-demo.env
ExecStart=/opt/kh-demo/run.sh
Restart=on-failure
RestartSec=5s
TimeoutStopSec=20s
SyslogIdentifier=kh-demo
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target
EOF

Secção [Unit]

Description= é o texto que aparece no systemctl status. Não tem efeito técnico, mas é o que decide se às três da manhã percebe o que é que falhou. Uma frase, não uma palavra.

After= define apenas a ordem, não a dependência. After=network-online.target significa: se este target for arrancado neste boot de qualquer forma, espera por ele. Não significa que ele seja arrancado.

Wants= é que o pede de facto, e ainda assim como dependência fraca. Se o target falhar, o serviço arranca à mesma. A contrapartida Requires= arrastaria o serviço para o erro. Para a maioria das aplicações, Wants= é a escolha certa, porque um serviço que nem chega a arrancar por causa de um timeout de rede é pior do que um serviço que corre por instantes no vazio e volta graças ao Restart=.

Secção [Service]

User= e Group= definem a identidade do processo. Sem estas linhas, o serviço corre como root. Em Debian e Ubuntu, o useradd cria por omissão um grupo com o mesmo nome, por isso Group=khdemo encaixa.

WorkingDirectory= é o diretório de trabalho do processo. Sem esta indicação, o serviço arranca em /. Qualquer programa que procure ficheiros de configuração, templates ou plugins de forma relativa ao diretório atual afunda-se logo aí. Se o diretório indicado não existir, o arranque termina com 200/CHDIR.

EnvironmentFile= carrega variáveis a partir de um ficheiro. O sinal de menos à frente do caminho torna o ficheiro opcional. Sem esse menos, o arranque falha quando o ficheiro não existe. O conteúdo são simples linhas KEY=VALUE, não é shell. Um export à frente está errado, e PORT=$BASE_PORT não é resolvido.

ExecStart= precisa de um caminho absoluto para o ficheiro executável. É nesta regra que a maioria tropeça, por isso mais abaixo tem uma secção só para ela.

TimeoutStopSec= limita quanto tempo o systemd espera depois do SIGTERM antes de chegar o SIGKILL. Por omissão são 90 segundos.

SyslogIdentifier= define o nome com que as linhas aparecem no journal. Sem esta linha, o remetente chama-se run.sh, o que é inútil na hora de filtrar.

NoNewPrivileges=true proíbe o processo e todos os seus filhos de ganharem privilégios através de binários setuid. PrivateTmp=true dá ao serviço um /tmp próprio. ProtectSystem=full monta /usr, /boot e /etc apenas em leitura. O nível strict vai mais longe e passa a exigir StateDirectory= ou ReadWritePaths= para tudo o que precise de escrita.

Secção [Install]

WantedBy=multi-user.target responde à pergunta de quando é que o serviço deve arrancar automaticamente. multi-user.target é o funcionamento multiutilizador normal sem interface gráfica, ou seja, aquilo que um servidor atinge. Só o systemctl enable avalia esta secção e cria o symlink. Se faltar o [Install], o enable aborta com The unit files have no installation config (WantedBy=, RequiredBy=, Also=, Alias= settings in the [Install] section, and DefaultInstance= for template units). O serviço ainda se deixa arrancar à mão, mas nunca mais volta depois de um reinício.

Type=simple, Type=exec e Type=forking

O Type= responde exatamente a uma pergunta: como é que o systemd reconhece que o serviço arrancou?

Com Type=simple, o serviço conta como arrancado assim que o processo é criado, ou seja, depois do fork() e ainda antes de o programa ser executado. É o valor por omissão e está correto para quase todos os programas modernos que ficam em primeiro plano.

Com Type=exec, o systemd espera ainda que o execve() tenha sido bem sucedido. A diferença prática é considerável: com Type=simple, o systemctl start comunica sucesso mesmo que o binário nem sequer exista, e o erro só aparece depois no journal. Com Type=exec, o arranque falha de imediato exatamente nesse caso. Disponível desde o systemd 240 e, portanto, nas quatro distribuições.

Com Type=forking, o systemd parte do princípio de que o programa se despede sozinho para segundo plano: o processo arrancado termina, um filho continua a correr, e só esse fim conta como sinal de arranque. É o comportamento clássico de um daemon Unix. Quem usa este tipo precisa quase sempre também de PIDFile= com caminho absoluto, caso contrário o systemd fica a adivinhar qual dos processos restantes é o processo principal.

A recomendação é clara: Type=forking só quando não há mesmo maneira de demover o programa disso. Quase todo o software tem uma opção para o efeito, muitas vezes --foreground, -D FOREGROUND, --no-daemon ou daemon off; na configuração. Em primeiro plano e com Type=simple, a unidade fica mais curta, os registos vão parar ao journal e o Restart= funciona de forma fiável.

A combinação errada típica: um programa que se coloca sozinho em segundo plano, debaixo de Type=simple. O systemd vê o processo de arranque terminar, dá o serviço por acabado e mata os filhos com ele. O sintoma é Active: inactive (dead) logo a seguir a um systemctl start que não comunicou qualquer erro. O caso inverso, um programa de primeiro plano debaixo de Type=forking, deixa o systemd à espera de um fim que nunca chega: Job for foo.service failed because a timeout was exceeded, ao fim de 90 segundos.

Ligar corretamente o After=network-online.target

É aqui que quase todos os tutoriais param cedo demais. O network.target significa apenas que a gestão de rede foi arrancada, não que exista um endereço IP configurado. Quem precisa de um endereço alcançável, por exemplo porque o programa se liga a um IP fixo, quer o network-online.target.

Só que este target não é atingido por si só. Pressupõe que esteja ativado um serviço de espera adequado, e esse varia consoante a gestão de rede:

systemctl list-unit-files 'systemd-networkd-wait-online.service' 'NetworkManager-wait-online.service' 'ifupdown-wait-online.service' --no-pager

No Ubuntu Server 22.04 e 24.04, a rede é gerida pelo netplan com systemd-networkd, aí o systemd-networkd-wait-online.service está ativo e o network-online.target tem um significado real. Numa instalação Debian clássica com ifupdown, o ifupdown-wait-online.service existe, mas não está ativado. O target passa então a contar como atingido de imediato, e o tempo de espera com que se está a contar não chega a acontecer.

No Debian 12 e 13 com ifupdown, o serviço de espera ativa-se uma única vez, se for preciso:

systemctl enable ifupdown-wait-online.service

Ativar dois serviços de espera ao mesmo tempo não é boa ideia, porque cada um fica à espera da sua própria gestão de rede e um deles vai forçosamente parar no timeout de 90 segundos. Essa é a causa mais frequente de um servidor que de repente demora mais um minuto e meio a arrancar. Mais robusto do que qualquer ordem de arranque é, de qualquer forma, um programa que aguenta uma ligação em falta no arranque, combinado com Restart=on-failure.

Restart, RestartSec e o travão de arranque

Restart=on-failure reinicia quando o código de saída é diferente de zero, perante um sinal como SIGSEGV e perante um timeout do watchdog, mas não depois de um exit 0 nem depois de um systemctl stop manual. Restart=always reinicia também depois de um fim limpo, e é essa a forma mais rápida de construir um ciclo infinito.

Contra isso, o systemd tem um travão incorporado: por omissão são permitidas cinco tentativas de arranque em dez segundos (StartLimitBurst=5, StartLimitIntervalSec=10s). A partir daí o systemd desiste e comunica:

kh-demo.service: Start request repeated too quickly.
kh-demo.service: Failed with result 'exit-code'.
Failed to start kh-demo.service - KernelHost Demo Worker.

O serviço fica então no estado failed e deixa de reagir mesmo ao systemctl start, até o contador ser reposto:

systemctl reset-failed kh-demo.service

Há dois pormenores que custam tempo com regularidade. Primeiro, StartLimitBurst= e StartLimitIntervalSec= pertencem à secção [Unit], não a [Service]. Na secção errada são ignorados, com aviso no journal, mas sem erro. Segundo, as chamadas manuais a systemctl restart também contam: quem reinicia cinco vezes enquanto depura aciona o travão sozinho.

Regra prática: StartLimitIntervalSec maior do que RestartSec vezes StartLimitBurst.

No Debian 13 e no Ubuntu 24.04 existem ainda RestartSteps= e RestartMaxDelaySec= (a partir do systemd 254) para tempos de espera com crescimento exponencial. No Debian 12 e no Ubuntu 22.04 estas diretivas não existem e são ignoradas.

Ativar, arrancar e provar que funciona

Antes do primeiro arranque compensa a verificação de sintaxe. Encontra erros de escrita nos nomes das diretivas, secções mal escritas e programas em falta, sem arrancar seja o que for:

systemd-analyze verify /etc/systemd/system/kh-demo.service

Nenhuma saída significa: está tudo em ordem. Um erro de escrita como WorkingDirectiry= produz, consoante a versão do systemd, Unknown key name 'WorkingDirectiry' in section 'Service', ignoring ou Unknown key 'WorkingDirectiry' in section [Service], ignoring. É aqui que nascem os erros silenciosos: o systemd ignora chaves desconhecidas, o serviço arranca, mas comporta-se de forma diferente do esperado.

systemctl daemon-reload

O daemon-reload volta a ler os ficheiros de unidade, mas não reinicia nada. Quem se esquece do reload recebe no systemctl status seguinte: Warning: The unit file, source configuration file or drop-ins of kh-demo.service changed on disk. Run 'systemctl daemon-reload' to reload units. Vale a pena decorar a ordem: primeiro daemon-reload, depois restart.

systemctl enable kh-demo.service
systemctl start kh-demo.service

As duas coisas de uma vez fazem-se com systemctl enable --now kh-demo.service.

Agora a parte que a maioria dos tutoriais deixa de fora: a prova de que aquilo funcionou mesmo e não apenas de que não apareceu um erro. Quatro provas independentes.

Primeiro, o symlink existe. Ele é todo o mecanismo por detrás do enable. Se faltar, o serviço não arranca depois do reinício, diga o status o que disser nesse momento.

ls -l /etc/systemd/system/multi-user.target.wants/kh-demo.service
systemctl is-enabled kh-demo.service

Segundo, o estado. O que decide é o que está entre parênteses na linha Active:. active (running) quer dizer que existe um processo a correr. active (exited) quer dizer que o programa terminou e já não está lá ninguém. Para um serviço permanente isso é um erro, mesmo que ao lado esteja verde.

systemctl status kh-demo.service --no-pager
systemctl is-active kh-demo.service

Terceiro, o journal. Não só se chegam linhas, mas se chegam as linhas certas.

journalctl -u kh-demo.service -n 20 --no-pager

Acompanhar em tempo real: journalctl -u kh-demo.service -f. Só o último boot: -b. Intervalo de tempo: --since "-1h". Mais sobre o tema no artigo journalctl: analisar logs sob o systemd.

Quarto, a configuração efetiva. Não o ficheiro, mas aquilo que o systemd fez dele. A diferença conta assim que houver drop-ins pelo meio.

systemctl show kh-demo.service -p ExecStart -p User -p WorkingDirectory -p Restart -p MainPID
systemctl cat kh-demo.service

A prova final continua a ser um reinício a sério. Depois dele, este comando mostra se ficou alguma coisa pelo caminho:

systemctl list-units --type=service --state=failed --no-pager

Os erros mais frequentes na sua forma literal

O systemd comunica os erros de arranque através de códigos de saída próprios acima de 200. O número que aparece no status já diz onde é preciso procurar.

CódigoSignificadoCausa
200/CHDIREXIT_CHDIRWorkingDirectory= não existe ou o utilizador não consegue entrar nele
203/EXECEXIT_EXECExecStart= não encontrado, não executável ou interpretador errado
216/GROUPEXIT_GROUPGroup= não existe
217/USEREXIT_USERUser= não existe
219/CGROUPEXIT_CGROUPnão foi possível criar a cgroup
238/STATE_DIRECTORYEXIT_STATE_DIRECTORYStateDirectory= já existe com o proprietário errado

203/EXEC: o caminho errado em ExecStart

No journal fica uma linha do género kh-demo.service: Failed at step EXEC spawning /opt/kh-demo/run.sh: No such file or directory, seguida de Main process exited, code=exited, status=203/EXEC.

No such file or directory é enganador, porque a mensagem tem três causas. Primeira: o ficheiro não existe mesmo, quase sempre por causa de um erro de escrita ou porque o binário está em /usr/local/bin em vez de /usr/bin. Segunda: falta o bit de execução, e nesse caso o journal comunica Permission denied. Terceira, o caso mais traiçoeiro: o ficheiro é executável, mas a sua linha shebang aponta para o vazio. Um script com #!/usr/bin/python falha exatamente assim no Debian 12 e mais recentes, porque aí só existe /usr/bin/python3. O kernel comunica a falta do interpretador, o systemd passa isso adiante como falta do script.

A verificação demora três segundos:

ls -l /opt/kh-demo/run.sh
head -n 1 /opt/kh-demo/run.sh

Caminhos relativos e PATH

ExecStart=node server.js não funciona, nem sequer com WorkingDirectory= definido. O erro ao carregar a unidade é Neither a valid executable name nor an absolute path. Só a primeira entrada tem de ser absoluta, os argumentos seguintes podem ficar relativos. O correto é ExecStart=/usr/bin/node server.js, sendo que o server.js é resolvido de forma relativa ao WorkingDirectory. O caminho certo é dado pelo command -v:

command -v bash

Atenção aos gestores de versões: com o nvm isso devolve um caminho como /root/.nvm/versions/node/v22.14.0/bin/node, que não existe para o utilizador do serviço. Os runtimes destinados a serviços instalam-se ao nível de todo o sistema, por exemplo através da NodeSource ou da Adoptium Temurin.

Variáveis de ambiente em falta

O clássico: numa sessão interativa o programa corre, como serviço não. A razão é que o systemd não arranca uma shell de login. Nem o /etc/profile nem o ~/.bashrc nem o ~/.profile são lidos, e tudo o que aí esteja definido com export falta no serviço. O PATH de um serviço de sistema é um caminho mínimo fixo, normalmente /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.

Por isso, as variáveis vão para a unidade ou para um ficheiro próprio:

cat > /etc/kh-demo.env <<'EOF'
GREETING=olá
LANG=pt_PT.UTF-8
EOF
chmod 640 /etc/kh-demo.env
chown root:khdemo /etc/kh-demo.env
systemctl restart kh-demo.service
journalctl -u kh-demo.service -n 5 --no-pager

Se na saída aparecer agora GREETING=olá em vez de GREETING=não definido, o ficheiro chegou ao destino. Também se pode verificar diretamente:

systemctl show kh-demo.service -p Environment -p EnvironmentFiles

Três armadilhas neste tipo de ficheiros: a substituição de comandos com $(...) não acontece, um $ com sentido literal tem de ser escrito como $$, e as aspas acabam dentro do valor, onde não têm nada que fazer.

Unit not found

Failed to start kh-demo.service: Unit kh-demo.service not found. significa quase sempre uma de três coisas: o ficheiro está no diretório errado, tem a extensão errada (.services em vez de .service) ou falta o daemon-reload. Um ls -l /etc/systemd/system/ esclarece os dois primeiros casos.

Diferenças entre Debian 13, Debian 12, Ubuntu 24.04 e 22.04

As bases, ou seja [Unit], [Service], [Install], Type=simple, Restart=, User= e WorkingDirectory=, são idênticas nos quatro sistemas. As diferenças estão nas zonas periféricas.

Diretivas disponíveis. O Ubuntu 22.04 traz o systemd 249, o Debian 12 o systemd 252, o Ubuntu 24.04 o systemd 255 e o Debian 13 o systemd 257. Tudo o que surgiu a partir da versão 253 falta nos dois sistemas mais antigos: Type=notify-reload (253) e ainda RestartSteps=, RestartMaxDelaySec= e RestartMode=direct (254). Não são assinaladas como erro, são simplesmente ignoradas. É assim que nascem unidades que num sistema fazem o pretendido e no outro fazem algo diferente, aparentemente sem motivo.

systemctl --version

Rede. O Ubuntu Server usa netplan com systemd-networkd, o Debian na instalação padrão usa ifupdown. Por isso, no Ubuntu o network-online.target tem significado sem qualquer intervenção e no Debian só depois de ativar o ifupdown-wait-online.service.

Caminhos de interpretadores e runtimes. É aqui que nasce a maior parte dos erros 203/EXEC quando se transporta uma unidade entre sistemas. O Node.js está no Debian 13 na versão 20.19, no Debian 12 na 18.20, no Ubuntu 24.04 na 18.19 e no Ubuntu 22.04 na 12.22. O PHP é 8.4 no Debian 13, 8.2 no Debian 12, 8.3 no Ubuntu 24.04 e 8.1 no Ubuntu 22.04. No Java o salto é o maior de todos: o Debian 13 fornece apenas openjdk-21-jre-headless, o Debian 12 apenas openjdk-17-jre-headless, ao passo que o Ubuntu 24.04 e o 22.04 fornecem 8, 11, 17 e 21. Quem copiar ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java do Debian 12 para o Debian 13 recebe 203/EXEC de forma garantida.

Bases de dados. O Debian não fornece mysql-server, aí corre sempre MariaDB. Uma unidade com After=mysql.service fica, portanto, à espera no Debian de um serviço que não existe, e arranca sem qualquer atraso, em silêncio e sem aviso. O correto aí é After=mariadb.service.

Alterar, reverter, limpar

As unidades próprias alteram-se diretamente no ficheiro, seguido de daemon-reload. Para unidades que vêm de um pacote usam-se antes drop-ins, para que a próxima atualização não varra a personalização:

systemctl edit kh-demo.service

Isso cria o ficheiro /etc/systemd/system/kh-demo.service.d/override.conf, onde ficam apenas as diretivas alteradas. Caso especial: as diretivas de lista, como ExecStart=, não são substituídas, são acrescentadas. Quem as quiser sobrepor tem de esvaziar a lista primeiro:

ExecStart=
ExecStart=/opt/kh-demo/run.sh --verbose

O systemctl revert kh-demo.service volta a remover todos os drop-ins. Desmontagem completa do exemplo, exatamente por esta ordem:

systemctl disable --now kh-demo.service
rm -f /etc/systemd/system/kh-demo.service
rm -rf /etc/systemd/system/kh-demo.service.d
systemctl daemon-reload
systemctl reset-failed

Atenção ao último passo: systemctl reset-failed sem argumento repõe o estado de erro de todas as unidades, não apenas o do serviço de exemplo. Num sistema onde existam ainda outros serviços no estado failed, desaparecem com isso também as mensagens desses serviços de systemctl list-units --state=failed. Quem só quiser limpar aqui usa a forma dirigida systemctl reset-failed kh-demo.service da secção mais acima.

Quem apagar o ficheiro sem antes chamar o disable deixa para trás um symlink morto em /etc/systemd/system/multi-user.target.wants/, que aparece como aviso em cada daemon-reload. Limpa-se com find /etc/systemd/system -xtype l -delete. Só que esta chamada remove todos os symlinks mortos abaixo de /etc/systemd/system, incluindo restos de outros serviços de que talvez ainda precise. Mais seguro é olhar primeiro sem o -delete, ou usar logo a variante dirigida find /etc/systemd/system -xtype l -name 'kh-demo*' -delete. Opcionalmente, ainda o utilizador e os ficheiros:

userdel khdemo
rm -rf /opt/kh-demo /etc/kh-demo.env

Caminho absoluto no ExecStart, Type adequado, utilizador próprio, WorkingDirectory definido, variáveis através de EnvironmentFile=: quem tiver isto tudo reunido tem um serviço que sobrevive a um reinício e que, em caso de erro, revela o que aconteceu.

Perguntas frequentes

Tenho de executar systemctl daemon-reload depois de cada alteração ao ficheiro de unidade?
Sim. O systemd mantém os ficheiros de unidade em memória, e uma alteração no disco só produz efeito depois do reload. Quem se esquece dele recebe no systemctl status seguinte o aviso "The unit file, source configuration file or drop-ins of ... changed on disk". A ordem é sempre primeiro systemctl daemon-reload, depois systemctl restart. O reload por si só não reinicia nada e não altera nada nos processos em execução.
O que significa status=203/EXEC?
O systemd não conseguiu executar o programa indicado em ExecStart. Há três causas possíveis: o ficheiro não existe (erro de escrita ou diretório errado), falta o bit de execução, ou a linha shebang de um script aponta para um interpretador que não existe, por exemplo #!/usr/bin/python no Debian 12 ou mais recente, onde só existe /usr/bin/python3. Verifique com ls -l sobre o ficheiro e head -n 1 sobre a primeira linha.
Porque é que o meu serviço não arranca depois do reinício, apesar de o systemctl start funcionar?
Quase sempre falta a secção [Install] com WantedBy=multi-user.target, ou o systemctl enable nunca chegou a ser executado. A prova é o symlink: ls -l /etc/systemd/system/multi-user.target.wants/nome.service tem de mostrar o ficheiro, e o systemctl is-enabled tem de devolver "enabled". Se a secção faltar, o enable aborta com "The unit files have no installation config".
Type=simple ou Type=forking?
Type=simple quando o programa fica em primeiro plano, o que se aplica a praticamente todas as aplicações modernas e é o valor por omissão. Type=forking apenas quando o programa se coloca obrigatoriamente sozinho em segundo plano, e nesse caso também com PIDFile=. A maioria dos daemons tem uma opção como --foreground ou --no-daemon, e com ela o Type=simple é a melhor escolha. Mal combinado, isso nota-se como "inactive (dead)" logo a seguir ao arranque ou como um timeout ao fim de 90 segundos.
Porque é que o meu serviço não encontra as suas variáveis de ambiente?
O systemd não arranca uma shell de login. Nem o /etc/profile nem o ~/.bashrc nem o ~/.profile são lidos, e tudo o que aí esteja definido com export falta. O PATH é um caminho mínimo fixo. Por isso, as variáveis pertencem à unidade como Environment= ou a um ficheiro carregado através de EnvironmentFile=. Aí ficam linhas puras KEY=VALUE sem export, e não há substituição de comandos. Controlo com systemctl show nome.service -p Environment.
O serviço deixou de reagir ao systemctl start, o que fazer?
Provavelmente entrou em ação o travão de arranque: por omissão são permitidas cinco tentativas de arranque em dez segundos, depois disso o journal comunica "Start request repeated too quickly" e o serviço fica em failed. O systemctl reset-failed nome.service repõe o contador. Atenção: as chamadas manuais a systemctl restart também contam, e quem reinicia várias vezes seguidas enquanto depura aciona o travão sozinho.

systemd Linux Debian Ubuntu Administração de servidores Arranque automático journalctl Ficheiro de unidade Servidor root