Depurando serviços systemd que falham com "Failed to start"
O que você 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, permissões, dependências e start-limit
Resumo rápido (a ordem de diagnóstico)
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/USERtêm significado específico do systemd) - Verifique o caminho do
ExecStart, permissão de execução, usuário e diretório de trabalho - Descarte dependências, start-limit e
daemon-reloadausente após edição
Premissas (ambiente alvo)
- Uma distro baseada em systemd (Ubuntu / Debian / RHEL / CentOS / Fedora, etc.)
myapp.serviceé usado como nome de exemplo; substitua pelo seu- Serviços do sistema (gerenciados pelo root) são o foco. Para serviços de usuário, adicione
--user
Por onde começar a procurar?
Conclusão: Comece com
systemctl status <servico>. O estadoActive:, o exit code doMain PIDe as últimas ~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 própria unit não está sendo encontrada.Active:--failedsignifica que iniciou e morreu.activating (auto-restart)significa que está preso em um loop de reinicialização.Result:--exit-code(saída não-zero) /timeout(início não 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 específico do systemd.
A saída do status trunca os logs finais na largura do terminal. Leia o texto completo com journalctl. O valor de Result: é seu primeiro ponto de ramificação.
Como ler o log de falha com journalctl?
Conclusão:
journalctl -xeu <servico>é o comando chave.-ulimita ao serviço,-epula para o final,-xadiciona dicas explicativas do systemd. O próprio erro da aplicação (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 código de origem do systemd como status=203/EXEC, a própria mensagem de erro da aplicação frequentemente aparece apenas no journal. Cruze ambos.
Quando o log está vazio ou desatualizado:
daemon-reloadausente: você editou a unit mas ela não foi aplicada (veja abaixo).- Desvio de relógio: se
--sincese comporta de forma estranha, suspeite do horário do servidor. - Serviços de usuário: use
journalctl --user -u myapp. Não aparecerá no journal do root.
O que significam os exit codes 203 / 200 / 217?
Conclusão: O systemd atribui exit codes dedicados 200-243 para falhas durante a configuração de inicialização. Os comuns são
203/EXEC(executável ausente ou sem permissão de execução),200/CHDIR(WorkingDirectory não existe) e217/USER(a contaUser=não existe). Eles são distintos dos códigos genéricos que uma aplicação retorna.
Leia status=NNN/NAME na linha Main PID. Um valor 2xx sinaliza "o systemd falhou antes da aplicação executar", o que quase sempre aponta a causa para a configuração da unit.
| status | Nome | Causa típica |
|---|---|---|
203/EXEC |
EXEC | Caminho ExecStart errado / sem permissão exec / shebang ruim |
200/CHDIR |
CHDIR | O diretório WorkingDirectory= não existe |
217/USER |
USER | O usuário nomeado em User= não existe |
1+ |
(app) | Um erro genérico da própria aplicação; 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 começar com um caminho absoluto. Um server simples ou um nome dependente do PATH não é permitido. O equivalente de command not found aparece como 203/EXEC.
Como verificar o conteúdo e a sintaxe da unit?
Conclusão: Não leia o arquivo original que você editou; leia o que está efetivamente em vigor com
systemctl cat <servico>. Ele também 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, dependências irresolúveis e ExecStart ausente. Nenhuma saída significa nenhum problema de sintaxe.
Erros comuns de configuração:
- Incompatibilidade de
Type=: definirType=forkingpara um processo que fica em foreground faz o systemd esperar por um filho que nunca vem, e então expirar. Se seu processo não faz fork e daemonize, useType=simple(o padrão). ExecStartrelativo: como acima, um caminho absoluto é necessário.- Variáveis de ambiente ausentes: o
.bashrcde um shell interativo não é lido. Defina-as explicitamente comEnvironment=ouEnvironmentFile=.
Se você usar Type=forking, adicione também um PIDFile=. Sem ele, o systemd pode rastrear o processo principal errado e relatar active enquanto o processo real já morreu. Em caso de dúvida, comece com Type=simple.
Por que minha edição não entra em vigor?
Conclusão: O systemd armazena os arquivos de unit em cache na memória. Se você não executar
systemctl daemon-reloadapós a edição, ele inicia com a definição antiga. Esta é a causa clássica 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 está ausente, systemctl cat mostra o conteúdo editado enquanto o comportamento de inicialização ainda usa a definição antiga -- uma inconsistência confusa. Faça de edição -> daemon-reload -> restart um hábito único.
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 permissões, dependências e timeouts
Conclusão: Quando a aplicação funciona manualmente mas falha como serviço, as causas usuais são privilégios insuficientes para o usuário de execução, um serviço de dependência que ainda não está ativo, ou um timeout de inicialização. Verifique as permissões de
User=,After=/Requires=eTimeoutStartSecem ordem.
Permissões (funciona manualmente, Permission denied como serviço)
systemctl start executa como User= (root por padrão). Se isso difere do usuário com o qual você 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 está no lado da aplicação/permissão. Se não, suspeite da configuração da unit. Para o básico de permissões, veja Corrigindo Permission denied.
Dependências (um serviço necessário ainda não está ativo)
Se o serviço precisa de um banco de dados ou network-online mas a ordenação não está garantida, ele morre com falha de conexão logo após iniciar.
[Unit]
After=network-online.target postgresql.service
Wants=network-online.target
After= controla apenas a ordenação; Requires=/Wants= expressam a dependência. Revise isso para falhas de "o alvo ainda não está lá".
Timeouts (Result: timeout)
Se status mostra timeout, o serviço não sinalizou "início completo" dentro do padrão de 90 segundos. Para serviços com inicialização pesada, aumente TimeoutStartSec= ou use Type=notify para sinalizar prontidao explicitamente.
Como lidar com o loop "start request repeated too quickly"?
Conclusão: Após um número definido de falhas em uma janela curta (padrão
StartLimitBurst=5dentro deStartLimitIntervalSec=10s), o systemd suprime novas inicializações 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 é um resultado, não a causa. A razão real está 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; não corrige a causa. Se você não resolver a falha primeiro, você reentra no mesmo loop e start-limit-hit retorna. Mantenha a ordem.
Checklist de diagnóstico
Conclusão: Trabalhe de cima para baixo -- status -> journalctl -> exit code -> conteúdo da unit -> daemon-reload -> permissões/dependências -> start-limit -- e você identificará quase qualquer
Failed to start.
Verifique cada item em ordem.
- [ ] Leu
Active:/Result:/status=NNNdesystemctl status myapp - [ ] Verificou o próprio erro da aplicação em
journalctl -xeu myapp - [ ] Identificou o exit code (
203/EXEC,200/CHDIR,217/USERsão problemas de configuração da unit) - [ ] Confirmou a unit efetiva com
systemctl cat myapp;ExecStarté um caminho absoluto - [ ] Validou a sintaxe com
systemd-analyze verify - [ ] Executou
systemctl daemon-reloadapós editar a unit - [ ]
Type=corresponde ao comportamento do processo (se ele faz fork) - [ ] Reproduziu com
sudo -u <User>para isolar permissões, dependências e timeouts - [ ] Limpou
start-limit-hitcomreset-failedsomente após corrigir a causa