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 UnidadeExtensãoPropósito
Service.serviceProcessos daemon e tarefas únicas
Timer.timerExecução agendada (substitui cron)
Socket.socketAtivação de sockets IPC e rede
Mount.mountPontos de montagem do sistema de arquivos
Target.targetGrupos 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-abnormal ou no
  • 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ãoSignificado
*-*-* 02:00:00Diariamente às 2 AM
Mon *-*-* 09:00:00Toda segunda-feira às 9 AM
*-*-01 00:00:00Primeiro dia de cada mês
*-*-* *:00/15:00A cada 15 minutos
hourlyA 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

RecursosystemdSysVinitUpstart
Inicialização paralelaSimNãoParcial
Gerenciamento de dependênciasDiretivas declarativasOrdenação manual com númerosBaseado em eventos
Supervisão de serviçosPolíticas de reinício integradasNenhuma (requer ferramentas externas)Diretiva respawn
Registrojournald (estruturado, indexado)syslog (arquivos de texto simples)syslog
Ativação por socketSimNãoNão
Controle de recursosIntegração com cgroupsNenhumNenhum
Temporizadores/agendamentoTimers integradosRequer cronRequer cron
Formato de configuraçãoArquivos de unidade estilo INIScripts de shellArquivos conf baseados em stanzas
Análise de inicializaçãosystemd-analyzeNenhumNenhum
Estado de adoçãoPadrão na maioria das distrosSistemas legadosDescontinuado

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ão node
  • Type=forking precisa de PIDFile: Se seu daemon faz fork, o systemd precisa rastrear o PID filho. Defina PIDFile=/run/myapp.pid e garanta que sua app o escreva
  • Arquivos Environment vs inline: Para muitas variáveis, use EnvironmentFile=/etc/myapp/env em vez de múltiplas linhas Environment=. O formato é KEY=value (sem export)
  • 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 que loginctl enable-linger esteja configurado)
  • Reload vs restart vs daemon-reload: systemctl restart myapp reinicia o processo. systemctl daemon-reload recarrega definições de arquivos de unidade. Após editar um arquivo de unidade, você precisa do daemon-reload primeiro
  • O target do WantedBy importa: Use multi-user.target para servidores sem interface gráfica, graphical.target para 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:

SintomaCausaSolução
code=exited, status=203/EXECBinário não encontrado ou não executávelVerifique o caminho com which, defina chmod +x
code=exited, status=217/USERO usuário especificado em User= não existeCrie o usuário com useradd -r -s /sbin/nologin
Start request repeated too quicklyLoop de reinício atingiu StartLimitBurstAumente RestartSec, corrija a falha subjacente, depois systemctl reset-failed
code=exited, status=200/CHDIRWorkingDirectory não existeCrie o diretório e corrija a propriedade
Serviço inicia mas porta não acessívelFirewall ou endereço de bind incorretoVerifique 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 execute daemon-reload após alterações
  • Use Requires= junto com After= 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=strict e PrivateTmp=true devem ser padrão em unidades de produção
  • Diagnostique com systemctl status, journalctl -u e systemd-analyze verify

Artigos Relacionados