Depurando servicos systemd que falham com "Failed to start"

Depurando servicos systemd que falham com "Failed to start"

O que voce vai aprender

  • Por que systemctl start falha com Failed to start e como isolar a causa
  • Onde procurar em status e journalctl
  • Os culpados usuais: exit codes, o caminho do ExecStart, permissoes, dependencias e start-limit

Resumo rapido (a ordem de diagnostico)

Quase todo Failed to start se resolve com este fluxo. Trabalhe de cima para baixo.

  1. Leia systemctl status para o estado e a linha Result
  2. Leia o log de falha bruto com journalctl -xeu
  3. Leia o exit code (203/EXEC, 200/CHDIR, 217/USER tem significado especifico do systemd)
  4. Verifique o caminho do ExecStart, permissao de execucao, usuario e diretorio de trabalho
  5. Descarte dependencias, start-limit e daemon-reload ausente apos edicao

Premissas (ambiente alvo)

  • Uma distro baseada em systemd (Ubuntu / Debian / RHEL / CentOS / Fedora, etc.)
  • myapp.service e usado como nome de exemplo; substitua pelo seu
  • Servicos do sistema (gerenciados pelo root) sao o foco. Para servicos de usuario, adicione --user

Por onde comecar a procurar?

Conclusao: Comece com systemctl status <servico>. O estado Active:, o exit code do Main PID e as ultimas ~10 linhas de log geralmente apontam para a causa. Se mostra failed ou activating (auto-restart), o caminho se divide.

$ systemctl status myapp
x myapp.service - My Application
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Fri 2026-06-05 10:00:01 UTC; 5s ago
   Main PID: 12345 (code=exited, status=203/EXEC)
        CPU: 4ms

Jun 05 10:00:01 host systemd[1]: myapp.service: Main process exited, code=exited, status=203/EXEC
Jun 05 10:00:01 host systemd[1]: myapp.service: Failed with result 'exit-code'.

O que ler:

  • Loaded: -- o caminho da unit e enabled / disabled. Se mostra not-found, a propria unit nao esta sendo encontrada.
  • Active: -- failed significa que iniciou e morreu. activating (auto-restart) significa que esta preso em um loop de reinicializacao.
  • Result: -- exit-code (saida nao-zero) / timeout (inicio nao completou no tempo) / signal (morto por um sinal) / start-limit-hit (reiniciou muitas vezes).
  • status=NNN/NAME -- o exit code. Como mostrado abaixo, valores 2xx carregam significado especifico do systemd.

A saida do status trunca os logs finais na largura do terminal. Leia o texto completo com journalctl. O valor de Result: e seu primeiro ponto de ramificacao.

Como ler o log de falha com journalctl?

Conclusao: journalctl -xeu <servico> e o comando chave. -u limita ao servico, -e pula para o final, -x adiciona dicas explicativas do systemd. O proprio erro da aplicacao (command not found, Permission denied, bind: address already in use) aparece aqui.

# Ler o final, limitado ao servico (mais comum)
$ journalctl -xeu myapp

# Limitar aos ultimos minutos
$ journalctl -u myapp --since "5 min ago"

# Limitar ao boot atual
$ journalctl -b -u myapp

Para um codigo de origem do systemd como status=203/EXEC, a propria mensagem de erro da aplicacao frequentemente aparece apenas no journal. Cruze ambos.

Quando o log esta vazio ou desatualizado:

  • daemon-reload ausente: voce editou a unit mas ela nao foi aplicada (veja abaixo).
  • Desvio de relogio: se --since se comporta de forma estranha, suspeite do horario do servidor.
  • Servicos de usuario: use journalctl --user -u myapp. Nao aparecera no journal do root.

O que significam os exit codes 203 / 200 / 217?

Conclusao: O systemd atribui exit codes dedicados 200-243 para falhas durante a configuracao de inicializacao. Os comuns sao 203/EXEC (executavel ausente ou sem permissao de execucao), 200/CHDIR (WorkingDirectory nao existe) e 217/USER (a conta User= nao existe). Eles sao distintos dos codigos genericos que uma aplicacao retorna.

Leia status=NNN/NAME na linha Main PID. Um valor 2xx sinaliza "o systemd falhou antes da aplicacao executar", o que quase sempre aponta a causa para a configuracao da unit.

status Nome Causa tipica
203/EXEC EXEC Caminho ExecStart errado / sem permissao exec / shebang ruim
200/CHDIR CHDIR O diretorio WorkingDirectory= nao existe
217/USER USER O usuario nomeado em User= nao existe
1+ (app) Um erro generico da propria aplicacao; leia o corpo do journal
# Isolando 203/EXEC: verificar o caminho e permissao de execucao
$ systemctl cat myapp | grep ExecStart
ExecStart=/opt/myapp/bin/server --config /etc/myapp.conf

$ ls -l /opt/myapp/bin/server      # existe? tem o bit x?
$ head -1 /opt/myapp/bin/server    # se e um script, verifique o shebang

ExecStart deve comecar com um caminho absoluto. Um server simples ou um nome dependente do PATH nao e permitido. O equivalente de command not found aparece como 203/EXEC.

Como verificar o conteudo e a sintaxe da unit?

Conclusao: Nao leia o arquivo original que voce editou; leia o que esta efetivamente em vigor com systemctl cat <servico>. Ele tambem mescla quaisquer overrides drop-in (*.d/*.conf). Valide a sintaxe mecanicamente com systemd-analyze verify.

# Mostrar a unit efetiva, incluindo drop-ins
$ systemctl cat myapp

# Validar a sintaxe e referencias da unit
$ systemd-analyze verify /etc/systemd/system/myapp.service

systemd-analyze verify avisa sobre diretivas desconhecidas, dependencias irresoluveis e ExecStart ausente. Nenhuma saida significa nenhum problema de sintaxe.

Erros comuns de configuracao:

  • Incompatibilidade de Type=: definir Type=forking para um processo que fica em foreground faz o systemd esperar por um filho que nunca vem, e entao expirar. Se seu processo nao faz fork e daemonize, use Type=simple (o padrao).
  • ExecStart relativo: como acima, um caminho absoluto e necessario.
  • Variaveis de ambiente ausentes: o .bashrc de um shell interativo nao e lido. Defina-as explicitamente com Environment= ou EnvironmentFile=.

Se voce usar Type=forking, adicione tambem um PIDFile=. Sem ele, o systemd pode rastrear o processo principal errado e relatar active enquanto o processo real ja morreu. Em caso de duvida, comece com Type=simple.

Por que minha edicao nao entra em vigor?

Conclusao: O systemd armazena os arquivos de unit em cache na memoria. Se voce nao executar systemctl daemon-reload apos a edicao, ele inicia com a definicao antiga. Esta e a causa classica de "corrigi mas recebo o mesmo erro".

$ sudo vim /etc/systemd/system/myapp.service
$ sudo systemctl daemon-reload      # <- pule isso e sua edicao e ignorada
$ sudo systemctl restart myapp

Quando o daemon-reload esta ausente, systemctl cat mostra o conteudo editado enquanto o comportamento de inicializacao ainda usa a definicao antiga -- uma inconsistencia confusa. Faca de edicao -> daemon-reload -> restart um habito unico.

Em vez de editar a unit com vim diretamente, use systemctl edit myapp (cria um drop-in) ou systemctl edit --full myapp (edita a unit inteira). Ao salvar, ele executa o equivalente do daemon-reload automaticamente, prevenindo estruturalmente o erro de reload esquecido.

Isolando permissoes, dependencias e timeouts

Conclusao: Quando a aplicacao funciona manualmente mas falha como servico, as causas usuais sao privilegios insuficientes para o usuario de execucao, um servico de dependencia que ainda nao esta ativo, ou um timeout de inicializacao. Verifique as permissoes de User=, After=/Requires= e TimeoutStartSec em ordem.

Permissoes (funciona manualmente, Permission denied como servico)

systemctl start executa como User= (root por padrao). Se isso difere do usuario com o qual voce testou, o acesso a arquivos, portas ou sockets pode falhar com Permission denied.

# Reproduzir manualmente como o usuario de execucao do servico
$ sudo -u myappuser /opt/myapp/bin/server --config /etc/myapp.conf

Se o erro se reproduzir, a causa esta no lado da aplicacao/permissao. Se nao, suspeite da configuracao da unit. Para o basico de permissoes, veja Corrigindo Permission denied.

Dependencias (um servico necessario ainda nao esta ativo)

Se o servico precisa de um banco de dados ou network-online mas a ordenacao nao esta garantida, ele morre com falha de conexao logo apos iniciar.

[Unit]
After=network-online.target postgresql.service
Wants=network-online.target

After= controla apenas a ordenacao; Requires=/Wants= expressam a dependencia. Revise isso para falhas de "o alvo ainda nao esta la".

Timeouts (Result: timeout)

Se status mostra timeout, o servico nao sinalizou "inicio completo" dentro do padrao de 90 segundos. Para servicos com inicializacao pesada, aumente TimeoutStartSec= ou use Type=notify para sinalizar prontidao explicitamente.

Como lidar com o loop "start request repeated too quickly"?

Conclusao: Apos um numero definido de falhas em uma janela curta (padrao StartLimitBurst=5 dentro de StartLimitIntervalSec=10s), o systemd suprime novas inicializacoes e relata start-limit-hit. Corrija a causa raiz, depois limpe o contador com systemctl reset-failed.

myapp.service: Start request repeated too quickly.
myapp.service: Failed with result 'start-limit-hit'.

Esta mensagem e um resultado, nao a causa. A razao real esta nos logs de falha anteriores. Passos:

# 1) Voltar ate a razao real da falha
$ journalctl -xeu myapp

# 2) Corrigir a causa (ExecStart / permissoes / dependencias, etc.)

# 3) Limpar o contador de falhas, depois iniciar
$ sudo systemctl reset-failed myapp
$ sudo systemctl start myapp

reset-failed apenas limpa o contador; nao corrige a causa. Se voce nao resolver a falha primeiro, voce reentra no mesmo loop e start-limit-hit retorna. Mantenha a ordem.

Checklist de diagnostico

Conclusao: Trabalhe de cima para baixo -- status -> journalctl -> exit code -> conteudo da unit -> daemon-reload -> permissoes/dependencias -> start-limit -- e voce identificara quase qualquer Failed to start.

Verifique cada item em ordem.

  • [ ] Leu Active: / Result: / status=NNN de systemctl status myapp
  • [ ] Verificou o proprio erro da aplicacao em journalctl -xeu myapp
  • [ ] Identificou o exit code (203/EXEC, 200/CHDIR, 217/USER sao problemas de configuracao da unit)
  • [ ] Confirmou a unit efetiva com systemctl cat myapp; ExecStart e um caminho absoluto
  • [ ] Validou a sintaxe com systemd-analyze verify
  • [ ] Executou systemctl daemon-reload apos editar a unit
  • [ ] Type= corresponde ao comportamento do processo (se ele faz fork)
  • [ ] Reproduziu com sudo -u <User> para isolar permissoes, dependencias e timeouts
  • [ ] Limpou start-limit-hit com reset-failed somente apos corrigir a causa

Proximas leituras