Depurando servicos systemd que falham com "Failed to start"
O que voce vai aprender
- Por que
systemctl startfalha com Failed to start e como isolar a causa - Onde procurar em
statusejournalctl - 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.
- Leia
systemctl statuspara o estado e a linha Result - Leia o log de falha bruto com
journalctl -xeu - Leia o exit code (
203/EXEC,200/CHDIR,217/USERtem significado especifico do systemd) - Verifique o caminho do
ExecStart, permissao de execucao, usuario e diretorio de trabalho - Descarte dependencias, start-limit e
daemon-reloadausente apos edicao
Premissas (ambiente alvo)
- Uma distro baseada em systemd (Ubuntu / Debian / RHEL / CentOS / Fedora, etc.)
myapp.servicee 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 estadoActive:, o exit code doMain PIDe as ultimas ~10 linhas de log geralmente apontam para a causa. Se mostrafailedouactivating (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 eenabled/disabled. Se mostranot-found, a propria unit nao esta sendo encontrada.Active:--failedsignifica 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.-ulimita ao servico,-epula para o final,-xadiciona 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-reloadausente: voce editou a unit mas ela nao foi aplicada (veja abaixo).- Desvio de relogio: se
--sincese 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) e217/USER(a contaUser=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 comsystemd-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=: definirType=forkingpara 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, useType=simple(o padrao). ExecStartrelativo: como acima, um caminho absoluto e necessario.- Variaveis de ambiente ausentes: o
.bashrcde um shell interativo nao e lido. Defina-as explicitamente comEnvironment=ouEnvironmentFile=.
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-reloadapos 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=eTimeoutStartSecem 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=5dentro deStartLimitIntervalSec=10s), o systemd suprime novas inicializacoes e relatastart-limit-hit. Corrija a causa raiz, depois limpe o contador comsystemctl 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=NNNdesystemctl status myapp - [ ] Verificou o proprio erro da aplicacao em
journalctl -xeu myapp - [ ] Identificou o exit code (
203/EXEC,200/CHDIR,217/USERsao problemas de configuracao da unit) - [ ] Confirmou a unit efetiva com
systemctl cat myapp;ExecStarte um caminho absoluto - [ ] Validou a sintaxe com
systemd-analyze verify - [ ] Executou
systemctl daemon-reloadapos 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-hitcomreset-failedsomente apos corrigir a causa