Gerenciar serviços em distribuições Linux modernas significa trabalhar com systemd, o sistema de inicialização e gerenciador de serviços que se tornou o padrão na maioria das distribuições. Este guia cobre tudo o que você precisa saber sobre criar unidades de serviço personalizadas, gerenciar dependências entre serviços, analisar logs com journalctl, substituir o cron por timers do systemd e diagnosticar problemas quando serviços não iniciam ou param inesperadamente.
Pré-requisitos
- Uma distribuição Linux executando systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+, Fedora 15+)
- Acesso ao terminal com privilégios sudo
- Compreensão básica de permissões de arquivos Linux e gerenciamento de processos
- Um editor de texto instalado (vim, nano ou similar)
Entendendo as Unidades do Systemd
O systemd organiza tudo em unidades — recursos que o sistema sabe como gerenciar. Os tipos de unidade mais comuns são:
| Tipo de Unidade | Extensão | Propósito |
|---|---|---|
| Service | .service | Processos daemon e tarefas únicas |
| Timer | .timer | Execução agendada (substitui cron) |
| Socket | .socket | Ativação de sockets IPC e rede |
| Mount | .mount | Pontos de montagem do sistema de arquivos |
| Target | .target | Grupos de unidades (como runlevels) |
Os arquivos de unidade ficam em três locais, com prioridade descendente:
/etc/systemd/system/ # Unidades personalizadas do admin (maior prioridade)
/run/systemd/system/ # Unidades em tempo de execução
/lib/systemd/system/ # Unidades padrão da distribuição
Para listar todas as unidades carregadas e seus estados:
systemctl list-units --type=service
systemctl list-units --type=service --state=failed
Criação de Unidades de Serviço Personalizadas
Um arquivo de unidade de serviço bem estruturado tem três seções. Aqui está um exemplo completo para uma aplicação Node.js:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStartPre=/usr/bin/node --check /opt/myapp/server.js
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# Endurecimento de segurança
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Diretivas-chave na seção [Service]:
- Type:
simple(padrão, processo em primeiro plano),forking(para daemons que fazem fork),oneshot(executa uma vez e termina),notify(sinaliza prontidão via sd_notify) - ExecStartPre: Comando para executar validação antes de iniciar o processo principal
- Restart:
on-failure,always,on-abnormalouno - RestartSec: Atraso entre tentativas de reinício em segundos
Após criar o arquivo, carregue e inicie:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
Gerenciamento de Dependências
O systemd usa várias diretivas para expressar relacionamentos entre unidades:
[Unit]
# Ordenação (quando iniciar, não se deve)
After=network-online.target # Iniciar após a rede estar pronta
Before=nginx.service # Iniciar antes do nginx
# Força da dependência
Requires=postgresql.service # Dependência forte — falha se postgres falhar
Wants=redis.service # Dependência fraca — continua se redis falhar
BindsTo=docker.service # Ciclo de vida vinculado — para quando docker para
# Resolução de conflitos
Conflicts=iptables.service # Não pode executar junto com iptables
A diferença entre After e Requires é crítica. After controla a ordenação (sequência) enquanto Requires controla a ativação (se deve incluir uma dependência). Normalmente você precisa de ambos:
Requires=postgresql.service
After=postgresql.service
Usar Requires sozinho não garante a ordem — ambos os serviços podem iniciar simultaneamente. Usar After sozinho não inclui a dependência — apenas os ordena se ambos estiverem iniciando.
Para visualizar a árvore de dependências de um serviço:
systemctl list-dependencies myapp.service
systemctl list-dependencies myapp.service --reverse
Análise de Logs com Journalctl
O journalctl é a ferramenta para consultar o journal do systemd. Aqui estão os padrões mais úteis:
# Acompanhar logs de um serviço específico em tempo real
journalctl -u myapp.service -f
# Mostrar logs desde a última inicialização
journalctl -u myapp.service -b
# Filtrar por intervalo de tempo
journalctl -u myapp.service --since "2026-02-20 08:00" --until "2026-02-20 18:00"
# Mostrar apenas erros e acima
journalctl -u myapp.service -p err
# Saída em formato JSON para processamento
journalctl -u myapp.service -o json-pretty --no-pager
# Mostrar mensagens do kernel relacionadas a OOM kills
journalctl -k --grep="Out of memory"
# Verificar uso de disco do journal
journalctl --disk-usage
# Limpar logs antigos para liberar espaço
sudo journalctl --vacuum-time=7d
sudo journalctl --vacuum-size=500M
Os níveis de prioridade seguem a convenção syslog: emerg (0), alert (1), crit (2), err (3), warning (4), notice (5), info (6), debug (7). Usar -p err mostra err e tudo mais severo.
Para configurar armazenamento persistente do journal (sobrevive a reinicializações):
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Timers do Systemd
Os timers do systemd substituem o cron com melhor registro, gerenciamento de dependências e confiabilidade. Um timer consiste em dois arquivos — o .timer e seu .service pareado:
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily database backup timer
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=900
Persistent=true
Unit=backup.service
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Database backup job
[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup-db.sh
StandardOutput=journal
Expressões comuns de OnCalendar:
| Expressão | Significado |
|---|---|
*-*-* 02:00:00 | Diariamente às 2 AM |
Mon *-*-* 09:00:00 | Toda segunda-feira às 9 AM |
*-*-01 00:00:00 | Primeiro dia de cada mês |
*-*-* *:00/15:00 | A cada 15 minutos |
hourly | A cada hora (abreviação) |
Habilitar e gerenciar timers:
sudo systemctl enable --now backup.timer
systemctl list-timers --all
systemctl status backup.timer
A diretiva Persistent=true garante que se o sistema estava desligado quando o timer deveria disparar, ele executa o trabalho imediatamente na próxima inicialização.
Comparação: Systemd vs SysVinit vs Upstart
| Recurso | systemd | SysVinit | Upstart |
|---|---|---|---|
| Inicialização paralela | Sim | Não | Parcial |
| Gerenciamento de dependências | Diretivas declarativas | Ordenação manual com números | Baseado em eventos |
| Supervisão de serviços | Políticas de reinício integradas | Nenhuma (requer ferramentas externas) | Diretiva respawn |
| Registro | journald (estruturado, indexado) | syslog (arquivos de texto simples) | syslog |
| Ativação por socket | Sim | Não | Não |
| Controle de recursos | Integração com cgroups | Nenhum | Nenhum |
| Temporizadores/agendamento | Timers integrados | Requer cron | Requer cron |
| Formato de configuração | Arquivos de unidade estilo INI | Scripts de shell | Arquivos conf baseados em stanzas |
| Análise de inicialização | systemd-analyze | Nenhum | Nenhum |
| Estado de adoção | Padrão na maioria das distros | Sistemas legados | Descontinuado |
Cenário Real
Você tem um servidor de produção executando uma API em Python que depende de PostgreSQL e Redis. A API deve iniciar após ambos os bancos de dados estarem prontos, reiniciar em caso de falhas com uma estratégia de back-off, e executar um trabalho de limpeza a cada 6 horas.
Unidade de serviço (/etc/systemd/system/api.service):
[Unit]
Description=Production Python API
After=network-online.target postgresql.service redis.service
Requires=postgresql.service
Wants=redis.service
[Service]
Type=simple
User=apiuser
WorkingDirectory=/opt/api
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/api/venv/bin/gunicorn -w 4 -b 0.0.0.0:8000 app:create_app()
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=300
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Timer de limpeza (/etc/systemd/system/api-cleanup.timer):
[Unit]
Description=API cleanup every 6 hours
[Timer]
OnBootSec=15min
OnUnitActiveSec=6h
Persistent=true
[Install]
WantedBy=timers.target
Esta configuração garante que a API só inicia quando o PostgreSQL está em execução (dependência forte) e opcionalmente usa Redis (dependência fraca). A política de reinício permite 5 tentativas de reinício dentro de 5 minutos antes de desistir.
Armadilhas e Casos Especiais
- ExecStart deve usar caminhos absolutos: Caminhos relativos causam falhas silenciosas. Sempre use
/usr/bin/node, nãonode - Type=forking precisa de PIDFile: Se seu daemon faz fork, o systemd precisa rastrear o PID filho. Defina
PIDFile=/run/myapp.pide garanta que sua app o escreva - Arquivos Environment vs inline: Para muitas variáveis, use
EnvironmentFile=/etc/myapp/envem vez de múltiplas linhasEnvironment=. O formato éKEY=value(semexport) - Serviços de usuário vs serviços do sistema: Unidades de usuário em
~/.config/systemd/user/executam sem sudo mas apenas enquanto o usuário está logado (a menos queloginctl enable-lingeresteja configurado) - Reload vs restart vs daemon-reload:
systemctl restart myappreinicia o processo.systemctl daemon-reloadrecarrega definições de arquivos de unidade. Após editar um arquivo de unidade, você precisa dodaemon-reloadprimeiro - O target do WantedBy importa: Use
multi-user.targetpara servidores sem interface gráfica,graphical.targetpara serviços de desktop
Solução de Problemas
Quando um serviço não inicia, siga este fluxo de diagnóstico:
# 1. Verificar estado atual e logs recentes
systemctl status myapp.service
# 2. Obter informações detalhadas da falha
journalctl -u myapp.service -e --no-pager -n 50
# 3. Validar sintaxe do arquivo de unidade
systemd-analyze verify /etc/systemd/system/myapp.service
# 4. Verificar ciclos de ordenação
systemd-analyze critical-chain myapp.service
# 5. Testar o comando ExecStart manualmente como o usuário do serviço
sudo -u appuser /usr/bin/node /opt/myapp/server.js
# 6. Verificar negações do SELinux ou AppArmor
sudo ausearch -m avc -ts recent 2>/dev/null || sudo journalctl -k --grep="apparmor"
# 7. Verificar permissões de arquivos
ls -la /opt/myapp/
namei -l /opt/myapp/server.js
Razões comuns de falha e soluções:
| Sintoma | Causa | Solução |
|---|---|---|
code=exited, status=203/EXEC | Binário não encontrado ou não executável | Verifique o caminho com which, defina chmod +x |
code=exited, status=217/USER | O usuário especificado em User= não existe | Crie o usuário com useradd -r -s /sbin/nologin |
Start request repeated too quickly | Loop de reinício atingiu StartLimitBurst | Aumente RestartSec, corrija a falha subjacente, depois systemctl reset-failed |
code=exited, status=200/CHDIR | WorkingDirectory não existe | Crie o diretório e corrija a propriedade |
| Serviço inicia mas porta não acessível | Firewall ou endereço de bind incorreto | Verifique com ss -tlnp, revise regras de firewall-cmd ou ufw |
Resumo
- Os arquivos de unidade do systemd usam seções declarativas estilo INI (
[Unit],[Service],[Install]) para definir o comportamento do serviço - Unidades personalizadas vão em
/etc/systemd/system/— sempre executedaemon-reloadapós alterações - Use
Requires=junto comAfter=para dependências fortes com ordenação correta - O journalctl fornece filtragem poderosa por unidade, prioridade, intervalo de tempo e formato de saída
- Os timers do systemd oferecem melhor confiabilidade que o cron com agendamento persistente e registro por trabalho
- Diretivas de endurecimento de segurança como
ProtectSystem=strictePrivateTmp=truedevem ser padrão em unidades de produção - Diagnostique com
systemctl status,journalctl -uesystemd-analyze verify