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.

SYSTEMD — GERENCIAMENTO DE SERVICOS NO LINUX nginx.service active (running) myapp.service active (running) backup.timer active (waiting) worker.service active (running) Gerencie todos os processos do seu servidor Linux a partir de um unico sistema init

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 sudo ou como root
  • Familiaridade basica com a linha de comando e um editor de texto (nano ou vim)
  • 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]

DiretivaProposito
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]

DiretivaProposito
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]

DiretivaProposito
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

TipoQuando UsarComo o systemd Rastreia a Disponibilidade
simpleMaioria das apps modernasConsidera a unidade iniciada assim que ExecStart faz fork
execComo simple, mas aguarda exec ter sucessoAguarda ate o binario executar com sucesso
forkingDaemons tradicionais que fazem forkAguarda o processo pai terminar
oneshotScripts que rodam uma vez e terminamAguarda ExecStart terminar antes de marcar como ativo
notifyApps que enviam notificacao de disponibilidadeAguarda 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

RecursosystemdSysVinit / init.dsupervisordPM2
Incluido no SOSim (todas as distros)Sim (legado)Nao (pip install)Nao (npm install)
Ordem de dependenciasGrafo completoOrdem manualLimitadoNao
Ativacao por socketSimNaoNaoNao
Limites de recursos cgroupSim (nativo)NaoNaoNao
Sandbox a nivel de kernelSim (extenso)NaoNaoNao
Logs centralizadosjournald (estruturado)syslog / arquivosApenas arquivosArquivos PM2
Jobs agendadosSim (unidades timer)NaoNaoSim (similar ao cron)
Integracao com Node.jsBoaFracaBoaExcelente
Curva de aprendizadoModeradaBaixaBaixaMuito 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-reload apos editar um arquivo unit
  • Use Type=simple para a maioria das apps modernas; use Type=notify para apps que sinalizam disponibilidade
  • Armazene segredos em um EnvironmentFile= separado com permissao 600, nao diretamente no arquivo unit
  • Configure politicas de reinicio com Restart=on-failure e StartLimitBurst para 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 journalctljournalctl -u nome-do-servico -f e seu primeiro recurso em qualquer falha

Artigos Relacionados