Tutorial: migrar tarefas agendadas de cron para systemd timers no Debian/Ubuntu

Linux & Sistemas · Por · Publicado em · Atualizado em · 9 min de leitura

Close-up de código HTML com destaque de sintaxe em editor de texto
Close-up de código em editor, representando trabalho técnico e automação Foto: Bibek ghosh / Pexels

Este passo a passo mostra como migrar tarefas agendadas do cron para systemd timers no Debian/Ubuntu com segurança. Você vai mapear a sintaxe de cron para OnCalendar, criar units .service e .timer, validar logs, configurar tolerâncias e ter um plano de rollback. O foco é prática e previsibilidade em produção.

Pré-requisitos e objetivos da migração

A migração para systemd timers traz rastreabilidade, controle de dependências e melhor observabilidade. Você terá logs centralizados via journal, facilidades para retentativas e agendamentos expressivos via OnCalendar.

Requisitos: acesso root ou sudo, editor de texto (nano/vi), e familiaridade básica com systemctl e permissões de arquivo. Se a tarefa for crítica, teste primeiro em ambiente de homologação.

Benefícios práticos:

  • Logs consolidados com journalctl e status por unit.
  • Execução com usuário dedicado, diretório de trabalho e variáveis de ambiente claros.
  • Agendamentos ricos (calendários, tolerâncias, atraso aleatório).
  • Persistência para rodar tarefas perdidas quando o host estava desligado.
  • Melhor integração com monitoramento e alertas.

1) Levantar tudo que está no cron (crontab e cron.d)

Antes de migrar, descubra todos os agendamentos ativos do sistema e de usuários. Faça um backup do conteúdo para referência. Não remova nada ainda — vamos migrar, validar e só então desligar o cron equivalente.

Se você tem jobs que interagem com banco de dados e podem causar bloqueios, faça uma revisão prévia do impacto de concorrência e janelas de manutenção; veja o guia de otimização de jobs cron que lidam com bloqueios em PostgreSQL em auditar e otimizar jobs cron.

# Listar crontab do root e de um usuário específico
sudo crontab -l || echo "(sem crontab do root)"
crontab -l -u appuser || echo "(sem crontab de appuser)"

# Listar definições em cron.d e arquivos de sistema
sudo ls -1 /etc/cron.d/ || true
sudo grep -R --line-number --color=never -E '^[^#].*' /etc/crontab /etc/cron.* 2>/dev/null || true

# Backup rápido dos crontabs
sudo crontab -l > ~/backup-root-crontab-$(date +%F).txt 2>/dev/null || true
crontab -l -u appuser > ~/backup-appuser-crontab-$(date +%F).txt 2>/dev/null || true

2) Mapear a agenda do cron para OnCalendar (systemd)

OnCalendar aceita expressões de calendário com precisão de segundos, intervalos e nomes de dias/meses. A tabela abaixo ajuda a converter padrões comuns de cron. Para verificar se a expressão está correta, use systemd-analyze calendar.

Lembre-se de timezone: o systemd usa a hora local do sistema. Ajuste com timedatectl se necessário.

Padrão cron Intenção OnCalendar equivalente (exemplo)
0 2 * * * Diário às 02:00 --* 02:00:00
0 2 * * 1-5 Dias úteis às 02:00 Mon..Fri --* 02:00:00
*/15 * * * * A cada 15 minutos *:0/15
0 */6 * * * A cada 6 horas --* 00,06,12,18:00:00
30 1 1 * * Todo dia 1 às 01:30 --01 01:30:00
# Testar como o systemd interpreta a expressão
systemd-analyze calendar "Mon..Fri *-*-* 02:00:00"
# Saída mostra a próxima execução e interpretação amigável

3) Criar a unit .service correspondente ao job

Cada cron job vira um .service com a ação real no ExecStart. Separe lógica em scripts versionados e use caminhos absolutos. Defina o usuário, diretório e variáveis conforme necessário. Evite rodar como root sem necessidade. Depois, ajuste permissões do arquivo para não expor segredos.

  • Salve em /etc/systemd/system/ se for de escopo de sistema.
  • Use User=appuser para jobs de aplicação.
  • Defina WorkingDirectory e Environment conforme o script.
# /etc/systemd/system/backup-db.service
[Unit]
Description=Backup do banco de dados
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=appuser
WorkingDirectory=/opt/app
# Exemplo de variáveis (opcional)
Environment=PGHOST=/var/run/postgresql
Environment=PGUSER=backup
# Caminho absoluto do script/comando
ExecStart=/usr/local/bin/backup-db.sh
# (Opcional) limites de recursos
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=6

[Install]
WantedBy=multi-user.target

4) Criar a unit .timer com OnCalendar e opções úteis

O .timer aponta para o .service e define quando rodar. Persistent garante execução de runs perdidos durante downtime. RandomizedDelaySec reduz thundering herd em múltiplos hosts. AccuracySec controla tolerância do disparo.

Se o .timer e o .service tiverem o mesmo nome-base, não é preciso definir Unit=, mas você pode manter para clareza.

  • OnCalendar define o agendamento principal.
  • Persistent=true executa atrasados ao religar.
  • RandomizedDelaySec adiciona jitter controlado.
# /etc/systemd/system/backup-db.timer
[Unit]
Description=Timer do backup do banco (dias úteis 02:00)

[Timer]
OnCalendar=Mon..Fri *-*-* 02:00:00
Persistent=true
RandomizedDelaySec=5m
AccuracySec=1min
Unit=backup-db.service

[Install]
WantedBy=timers.target

5) Carregar, habilitar e testar (dry‑run controlado)

Após criar os arquivos, recarregue o systemd, habilite e inicie o timer. Para testar a lógica, dispare manualmente o .service. Não remova o cron original até validar a execução completa, para evitar janelas sem job ou execuções duplicadas.

  • Habilite o timer para iniciar no boot.
  • Use systemctl list-timers para conferir o agendamento.
  • Dispare o .service manualmente para teste imediato.
sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer

# Listar timers e ver a próxima execução
systemctl list-timers --all | grep backup-db || true

# Teste manual do serviço
sudo systemctl start backup-db.service
sudo systemctl status backup-db.service --no-pager

6) Validar execução e logs com journalctl

Acompanhe logs por unit e confirme variáveis, caminhos e códigos de saída. Verifique também o timezone e se o host mantém hora correta via NTP. Para padronizar logs de aplicações, considere estruturar saídas em JSON; veja ideias em logs estruturados (JSON).

  • journalctl -u mostra histórico por serviço.
  • status exibe últimas linhas e código de retorno.
  • Confirme timezone e NTP para agendamentos corretos.
# Logs e status
date && timedatectl | sed -n '1,6p'
journalctl -u backup-db.service -n 100 --no-pager
journalctl -u backup-db.timer -n 50 --no-pager

# Última/Próxima execução do timer
systemctl status backup-db.timer --no-pager

7) Rollback seguro para cron (se algo der errado)

Se a migração não atender ao esperado, faça rollback sem pressa. Desative o timer antes de reativar o cron, evitando execuções em duplicidade. Documente o motivo e ajustes necessários. Só remova os units após estabilizar o cron novamente.

  • Desative e pare o timer.
  • Reative a linha correspondente no crontab.
  • Remova units e recarregue o systemd.
# Desativar timer e serviço
sudo systemctl disable --now backup-db.timer
sudo systemctl stop backup-db.service || true

# Reativar no crontab (edite e descomente a linha antiga)
sudo crontab -e

# Opcional: remover units e recarregar
sudo rm -f /etc/systemd/system/backup-db.{timer,service}
sudo systemctl daemon-reload

8) Verificação final e solução de problemas comuns

Use ferramentas de diagnóstico do systemd para confirmar a agenda e investigar erros. Problemas frequentes incluem caminhos relativos em ExecStart, permissões de arquivo, variáveis ausentes e timezone incorreto. Para hosts novos, confira tuning e segurança básicos em checklist de segurança para VPS.

  • Use systemd-analyze calendar para checar a expressão.
  • Garanta caminhos absolutos e permissão de execução do script.
  • Ajuste User= e WorkingDirectory quando necessário.
  • Se perdeu execuções, ative Persistent=true.
  • Verifique conflitos com cron ainda habilitado.
  • Sincronize hora com NTP e confirme timezone.
# Confirmar parsing da expressão do timer
systemd-analyze calendar "Mon..Fri *-*-* 02:00:00"

# Conferir unidades com erro
a=($(systemctl --failed --type=service --no-legend | awk '{print $1}')); printf '%s
' "${a[@]}"

# Timers ativos e próximos disparos
systemctl list-timers --all --no-pager

Perguntas frequentes

Por que usar systemd timers em vez de cron?

Systemd timers oferecem logs integrados, dependências, tolerância a atrasos (Persistent), jitter para evitar picos simultâneos e controle detalhado por unit. Isso facilita observabilidade e automação de operações, especialmente em ambientes com muitos hosts e requisitos de conformidade.

Como converter uma expressão cron complexa para OnCalendar?

Divida a intenção em regras de calendário explícitas ou múltiplas linhas OnCalendar. Use nomes de dias/meses e intervalos. Valide com systemd-analyze calendar para ver a próxima execução e confirmar o parsing. Quando necessário, crie mais de um timer para cobrir variações.

Timers rodam com qual usuário?

Defina o usuário na unit .service com User=appuser. Evite root quando possível. Se preferir timers por usuário, use units em ~/.config/systemd/user e gerencie com systemctl --user. Garanta permissões corretas para scripts, diretórios e arquivos de log.

Como registrar saída e erros do meu job?

Por padrão, stdout e stderr vão para o journal. Consulte com journalctl -u minha-unit.service. Se quiser arquivos dedicados, direcione a saída no script ou use StandardOutput e StandardError. Considere logs estruturados para facilitar filtros e correlação.

E se o servidor estava desligado na hora do job?

Habilite Persistent=true no .timer. Ao religar o host, o systemd executa as instâncias perdidas desde a última verificação. Combine com RandomizedDelaySec para distribuir a carga e evite thundering herd em clusters.

Posso desabilitar o cron após migrar tudo?

Sim, após migrar, validar e documentar a equivalência de todos os jobs, você pode desabilitar a entrada correspondente no cron. Faça isso gradualmente, monitorando logs e resultados, para evitar lacunas de execução ou duplicidades.

Precisa revisar sua migração de cron para systemd?

Se você quer analisar casos específicos, mapear agendamentos complexos e validar rollback, entre em contato para discutirmos o cenário e os próximos passos.

Falar sobre sua migração

Veja também