TL;DR — Resumo Rápido
Domine servicos systemd no Linux: anatomia de unit files, exemplos com Node.js e Python, politicas de reinicio, limites de recursos e seguranca avancada.
Manter processos de longa execucao em um servidor Linux costumava exigir scripts SysVinit complicados, configuracoes de supervisor ou hacks frageis em shell. O systemd mudou tudo isso. Como sistema init padrao em todas as principais distribuicoes Linux — Ubuntu, Debian, RHEL, Fedora, Arch — o systemd oferece uma maneira unificada e declarativa de definir exatamente como qualquer processo deve iniciar, parar, reiniciar, registrar logs e interagir com o restante do sistema.
Este guia abrange tudo o que voce precisa para criar servicos systemd personalizados prontos para producao: a anatomia de um arquivo unit, exemplos concretos para aplicacoes Node.js e Python, tipos de servico, politicas de reinicio, limites de recursos, diretivas de seguranca, ativacao por socket, unidades de temporizador como alternativa ao cron e como depurar falhas com journalctl.
Pre-requisitos
Antes de comecar, certifique-se de ter:
- Um sistema Linux com systemd (Ubuntu 16.04+, Debian 8+, CentOS 7+, RHEL 7+, Fedora 15+ ou qualquer distribuicao moderna)
- Um terminal com acesso
sudoou como root - Familiaridade basica com a linha de comando e um editor de texto (
nanoouvim) - Uma aplicacao que deseja executar como servico (Node.js, Python, binario Go, etc.)
Verifique que o systemd esta em execucao:
systemctl --version
# Deve exibir: systemd 249 (ou numero de versao similar)
Anatomia do Arquivo Unit
Cada servico gerenciado pelo systemd e descrito por um arquivo unit — um arquivo de configuracao em texto simples no formato INI. Os arquivos unit de servico personalizados ficam em /etc/systemd/system/. O nome do arquivo termina em .service.
Um arquivo unit completo tem tres secoes:
[Unit]
Description=Meu Servico de Aplicacao
Documentation=https://exemplo.com/docs
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=minhaapp
Group=minhaapp
WorkingDirectory=/opt/minhaapp
ExecStart=/usr/bin/node /opt/minhaapp/server.js
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
A Secao [Unit]
| Diretiva | Proposito |
|---|---|
Description= | Nome legivel exibido na saida do systemctl status |
After= | Inicia esta unidade apos as unidades listadas estarem ativas |
Wants= | Dependencia suave — unidades listadas sao iniciadas, mas falha e tolerada |
Requires= | Dependencia forte — se a unidade listada falhar, esta tambem falha |
A Secao [Service]
| Diretiva | Proposito |
|---|---|
Type= | Modelo de ciclo de vida do processo (simple, forking, oneshot, notify) |
User= / Group= | Executa o processo como este usuario/grupo |
ExecStart= | O comando a executar (deve ser caminho absoluto) |
ExecStartPre= | Comandos a executar antes do ExecStart |
Restart= | Quando reiniciar automaticamente |
EnvironmentFile= | Carrega variaveis de ambiente de um arquivo |
StandardOutput= | Para onde enviar stdout (journal, file:, null) |
A Secao [Install]
| Diretiva | Proposito |
|---|---|
WantedBy= | Qual alvo habilita este servico (geralmente multi-user.target) |
Criar um Servico Node.js
Suponha que voce tenha uma API Node.js em /opt/minhaapi/server.js escutando na porta 3000.
Passo 1: Criar um usuario dedicado
sudo useradd --system --no-create-home --shell /usr/sbin/nologin nodeapi
sudo chown -R nodeapi:nodeapi /opt/minhaapi
Passo 2: Criar o arquivo unit
sudo nano /etc/systemd/system/minhaapi.service
[Unit]
Description=Meu Servico API Node.js
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=nodeapi
Group=nodeapi
WorkingDirectory=/opt/minhaapi
EnvironmentFile=/etc/minhaapi/environment
ExecStart=/usr/bin/node /opt/minhaapi/server.js
ExecStartPre=/usr/bin/node --check /opt/minhaapi/server.js
Restart=on-failure
RestartSec=5s
TimeoutStopSec=10s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=minhaapi
MemoryMax=512M
CPUQuota=50%
LimitNOFILE=65536
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadWritePaths=/opt/minhaapi/logs /var/lib/minhaapi
[Install]
WantedBy=multi-user.target
Passo 3: Criar o arquivo de ambiente
sudo mkdir -p /etc/minhaapi
sudo nano /etc/minhaapi/environment
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://usuario:senha@localhost/meubanco
JWT_SECRET=seu-segredo-aqui
sudo chmod 600 /etc/minhaapi/environment
Passo 4: Habilitar e iniciar
sudo systemctl daemon-reload
sudo systemctl enable minhaapi.service
sudo systemctl start minhaapi.service
sudo systemctl status minhaapi.service
Criar um Servico Python
Para um worker Python em /opt/worker/worker.py:
[Unit]
Description=Worker Python em Segundo Plano
After=network.target redis.service
Requires=redis.service
[Service]
Type=simple
User=pyworker
Group=pyworker
WorkingDirectory=/opt/worker
ExecStart=/opt/worker/venv/bin/python -u worker.py
EnvironmentFile=/etc/worker/environment
Restart=on-failure
RestartSec=10s
StartLimitIntervalSec=60s
StartLimitBurst=3
StandardOutput=journal
StandardError=journal
MemoryMax=256M
CPUQuota=25%
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
[Install]
WantedBy=multi-user.target
O flag -u no comando Python desabilita o buffering de saida para que os logs aparecam no journald imediatamente.
Tipos de Servico
| Tipo | Quando Usar | Como o systemd Rastreia a Disponibilidade |
|---|---|---|
simple | Maioria das apps modernas | Considera a unidade iniciada assim que ExecStart faz fork |
exec | Como simple, mas aguarda exec ter sucesso | Aguarda ate o binario executar com sucesso |
forking | Daemons tradicionais que fazem fork | Aguarda o processo pai terminar |
oneshot | Scripts que rodam uma vez e terminam | Aguarda ExecStart terminar antes de marcar como ativo |
notify | Apps que enviam notificacao de disponibilidade | Aguarda a notificacao antes de marcar como ativo |
Politicas de Reinicio e Limites de Recursos
Politicas de Reinicio
Restart=always # Reinicia sempre que o servico termina
Restart=on-failure # Reinicia apenas em falha (nao em saida limpa)
Restart=on-abnormal # Reinicia em falha + timeout watchdog + sinais anormais
Restart=no # Nunca reinicia
Com limitacao de taxa para evitar que um servico com falhas sobrecarregue o sistema:
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=5
Limites de Recursos
MemoryMax=1G # Limite rigido de memoria
MemoryHigh=800M # Limite suave de memoria
MemorySwapMax=0 # Desabilitar swap para este servico
CPUQuota=200% # Limitar a 2 nucleos CPU completos
LimitNOFILE=65536 # Maximo de descritores de arquivo abertos
LimitNPROC=512 # Maximo de processos/threads
Endurecimento de Seguranca
[Service]
NoNewPrivileges=true # Impede novos privilegios via setuid/setgid
ProtectSystem=strict # Monta /usr, /boot, /etc como somente leitura
PrivateTmp=true # /tmp privado para este servico
DynamicUser=true # Usuario dinamico sem privilegios
ProtectHome=true # Bloqueia acesso a diretorios /home
SystemCallFilter=@system-service # Restringe chamadas de sistema
ProtectKernelTunables=true # Impede escrita em variaveis do kernel
ProtectKernelModules=true # Impede carregamento de modulos do kernel
Analise a pontuacao de seguranca de qualquer servico em execucao:
systemd-analyze security minhaapi.service
Unidades de Temporizador: systemd como Alternativa ao Cron
sudo nano /etc/systemd/system/backup-noturno.service
[Unit]
Description=Backup Noturno do Banco de Dados
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-db.sh
StandardOutput=journal
sudo nano /etc/systemd/system/backup-noturno.timer
[Unit]
Description=Executar Backup Noturno as 2h
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup-noturno.timer
systemctl list-timers --all
systemd vs Outras Ferramentas de Gerenciamento de Processos
| Recurso | systemd | SysVinit / init.d | supervisord | PM2 |
|---|---|---|---|---|
| Incluido no SO | Sim (todas as distros) | Sim (legado) | Nao (pip install) | Nao (npm install) |
| Ordem de dependencias | Grafo completo | Ordem manual | Limitado | Nao |
| Ativacao por socket | Sim | Nao | Nao | Nao |
| Limites de recursos cgroup | Sim (nativo) | Nao | Nao | Nao |
| Sandbox a nivel de kernel | Sim (extenso) | Nao | Nao | Nao |
| Logs centralizados | journald (estruturado) | syslog / arquivos | Apenas arquivos | Arquivos PM2 |
| Jobs agendados | Sim (unidades timer) | Nao | Nao | Sim (similar ao cron) |
| Integracao com Node.js | Boa | Fraca | Boa | Excelente |
| Curva de aprendizado | Moderada | Baixa | Baixa | Muito baixa |
Cenario Real
Voce tem um servidor de producao com uma aplicacao Python FastAPI processando trabalhos de uma fila Redis. O servico deve reiniciar automaticamente em caso de falha e nunca consumir mais de 512 MB de RAM.
[Unit]
Description=FastAPI Job Worker
After=network.target redis.service
Requires=redis.service
StartLimitIntervalSec=120s
StartLimitBurst=4
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/fastapi-worker
ExecStart=/opt/fastapi-worker/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
EnvironmentFile=/etc/fastapi-worker/environment
Restart=on-failure
RestartSec=8s
TimeoutStartSec=30s
TimeoutStopSec=15s
StandardOutput=journal
SyslogIdentifier=fastapi-worker
MemoryMax=512M
CPUQuota=100%
LimitNOFILE=32768
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadWritePaths=/opt/fastapi-worker/data
[Install]
WantedBy=multi-user.target
Armadilhas e Casos Especiais
ExecStart deve ser caminho absoluto. Nao use shell built-ins, pipes ou redirecionamentos diretamente no ExecStart. Encapsule comandos complexos em um script.
Expansao de variaveis de ambiente. O systemd expande $VAR no ExecStart quando definido via Environment= ou EnvironmentFile=, mas NAO carrega arquivos de inicializacao do shell. Seu ~/.bashrc nunca e carregado.
Sinais de porcentagem em arquivos unit. Em arquivos unit, % e um caractere especificador. Para usar um sinal de porcentagem literal, escape-o como %%.
DynamicUser e permissoes de arquivo. Com DynamicUser=true, o UID alocado muda entre reinicializacoes. Use diretivas como StateDirectory= ou LogsDirectory= para que o systemd gerencie os diretorios persistentes corretamente.
After= vs Requires=. After= controla apenas a ordem — nao cria dependencia. Requires= cria a dependencia mas nao controla a ordem. Voce quase sempre vai querer os dois juntos.
Solucao de Problemas
O servico nao inicia
sudo systemctl status minhaapi.service
journalctl -u minhaapi.service -b
journalctl -u minhaapi.service --since "10 minutes ago"
journalctl -u minhaapi.service -f
O servico inicia mas cai imediatamente
journalctl -u minhaapi.service -n 100 --no-pager
ls -la /usr/bin/node
sudo -u nodeapi /usr/bin/node /opt/minhaapi/server.js
Reiniciar um servico com falha
sudo systemctl reset-failed minhaapi.service
sudo systemctl start minhaapi.service
Verificar exposicao de seguranca
systemd-analyze security minhaapi.service
systemd-analyze verify /etc/systemd/system/minhaapi.service
Resumo
- Os arquivos unit ficam em
/etc/systemd/system/e tem tres secoes:[Unit],[Service]e[Install] - Execute sempre
systemctl daemon-reloadapos editar um arquivo unit - Use
Type=simplepara a maioria das apps modernas; useType=notifypara apps que sinalizam disponibilidade - Armazene segredos em um
EnvironmentFile=separado com permissao600, nao diretamente no arquivo unit - Configure politicas de reinicio com
Restart=on-failureeStartLimitBurstpara sobreviver a falhas transitorias - Aplique diretivas de seguranca em cada servico de producao
- Substitua cron jobs por unidades de temporizador para melhor registro de logs
- Depure com journalctl —
journalctl -u nome-do-servico -fe seu primeiro recurso em qualquer falha