Tutorial: migrar tarefas agendadas de cron para systemd timers no Debian/Ubuntu
Linux & Sistemas · Por Kevin Marcondes · Publicado em · Atualizado em · 9 min de leitura
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çãoVeja também
- tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP
- Tutorial: configurar HAProxy com Let's Encrypt e healthchecks para balanceamento de tráfego entre containers Docker em VPS (passo a passo)
- Tutorial prático: implementar mTLS entre Nginx e microserviços usando smallstep CA em Docker Compose
- Tutorial: configurar cgroup v2 e systemd ResourceControl para limitar CPU, memória e I/O de containers Docker no Debian