Coldwast
Gerador de ficheiros de serviço systemd
Indique o comando que arranca a sua aplicação e obtenha uma unidade .service para colocar em /etc/systemd/system/, com os comandos de instalação correspondentes. O gerador também relê o que escreveu e assinala os erros que fazem uma unidade falhar logo no primeiro arranque. Tudo corre no seu navegador.

O seu ficheiro de unidade
O que o gerador detetou
Instalá-lo
As cinco linhas que decidem se funciona
ExecStart não é uma shell. O systemd divide a linha em argumentos por si próprio e executa o programa diretamente. Um pipe, uma redireção > ou um && chega ao seu programa como argumento literal. O systemd também expande $VAR por conta própria, por isso um cifrão literal escreve-se $$. Quando o gerador deteta sintaxe de shell, envolve o comando em /bin/sh -c e faz esse escape por si.
Use um caminho absoluto. Um caminho como ./server é rejeitado de imediato. Um nome sem caminho como node é aceite, mas o systemd procura apenas nos seus próprios diretórios fixos, não no PATH da sua shell, por isso um Node instalado com nvm ou uma ferramenta em ~/.local/bin não é encontrado.
Type=simple ou exec para quase tudo. Um servidor web, um worker ou um bot que fica em primeiro plano é simple. exec difere num ponto útil: o arranque só é dado como bem-sucedido quando o binário foi realmente executado, por isso um caminho errado faz falhar systemctl start em vez de falhar em silêncio um momento depois. forking é para daemons antigos que passam sozinhos para segundo plano, e notify só funciona se o programa chamar sd_notify.
Restart=on-failure, com um limite. on-failure reinicia após uma falha ou uma saída diferente de zero, mas não após uma paragem limpa; always reinicia mesmo nesse caso. Por omissão o systemd permite 5 arranques em 10 segundos e, com uma espera de 5 segundos, uma app que falha nunca atinge esse limite: entra num ciclo infinito. StartLimitIntervalSec=300 com StartLimitBurst=5 faz com que desista e deixe um estado de falha claro.
Wants= e After= juntos. After=network-online.target sozinho apenas define uma ordem; não ativa o target. Sem Wants=network-online.target o seu serviço pode arrancar antes de a rede estar pronta: o clássico «funciona depois de um reinício manual, falha no arranque da máquina».
O isolamento, e o que ficou de fora de propósito
Com ProtectSystem=strict todo o sistema de ficheiros fica só de leitura para o serviço, exceto os caminhos indicados em ReadWritePaths=. PrivateTmp dá-lhe o seu próprio /tmp, NoNewPrivileges impede-o de ganhar privilégios através de binários setuid, e as definições de kernel e cgroup impedem-no de alterar o estado do sistema. Execute systemd-analyze security suaapp.service antes e depois: mostra uma pontuação de exposição e lista cada definição ainda aberta.
Duas recomendações comuns estão ausentes de propósito. MemoryDenyWriteExecute=true parte os compiladores just-in-time, ou seja Node.js, a JVM e PyPy. PrivateDevices=true não causa problemas numa app web, mas parte tudo o que fala com uma GPU ou uma porta série. Acrescente-as se souber que o seu programa não precisa delas.

Quando não arranca: os códigos de saída
systemctl status suaapp mostra um estado como status=203/EXEC. O número indica que passo falhou antes mesmo de o seu programa correr:
| Estado | Significado | Correção habitual |
|---|---|---|
203/EXEC | Não foi possível executar o programa | Caminho errado, falta de permissão de execução ou script sem linha shebang |
200/CHDIR | WorkingDirectory inexistente ou inacessível | Criá-lo, ou verificar que ProtectHome não o esconde |
217/USER | A conta de User= não existe | Executar a linha useradd acima |
226/NAMESPACE | Não foi possível montar o isolamento | Um caminho de ReadWritePaths não existe: criá-lo ou prefixá-lo com - |
Qualquer outra falha é o seu próprio programa a terminar, e a saída dele está em journalctl -u suaapp -b.
Alterá-lo mais tarde
Depois de editar o ficheiro em /etc/systemd/system/, execute sudo systemctl daemon-reload antes de reiniciar, caso contrário o systemd continua a usar a versão antiga. Para uma unidade instalada por um pacote, não a edite no local: sudo systemctl edit suaapp cria um ficheiro de substituição que sobrevive às atualizações e recarrega o systemd por si.
Relacionado: o que é um VPS, colocar um proxy reverso à frente da app, o básico de SSH e, para tarefas agendadas, o explicador de expressões cron.