Como configurar tarefas que rodam a cada 3 anos em sistemas reais
A maioria dos tutoriais fala de cron jobs diários ou mensais. Raramente alguém comenta o que acontece quando você precisa realmente de algo que execute a cada três anos. Vou explicar do jeito que funciona na prática.
O que é uma tarefa para 3 anos no contexto de infraestrutura
São jobs agendados com intervalo trienal que aparecem em cenários bem específicos: renovação de certificados SSL com validity de 3 anos (ainda existem CAs que emitem assim), rotação de chaves de criptografia para dados sensíveis, migração planejada de schemas de banco de dados, revisão de dependências legadas com ciclo de suporte longo, ou atualização de configurações de compliance regulatório. O problema é que a ferramenta padrão do Unix, o cron, não suporta naturalmente um intervalo de 3 anos. O campo mês no crontab vai de 1 a 12 e o dia de 1 a 31. Você precisa compor a lógica de outra forma.
No começo eu tentava colocar uma linha no crontab que rodasse todo ano em março, com um script interno verificando se o ano era divisível por 3. Isso deu problema porque anos bissextos e feriados quebravam a suposição de que "todo ano é igual". A correção foi deixar o systemd cuidar do agendamento.
Implementando com systemd timers
O systemd permite criar timers com precisão suficiente para intervalos longos. Aqui está a estrutura mínima que eu uso: Primeiro, o service file em /etc/systemd/system/triennial-task.service:
[Unit]
Description=Tarefa executada a cada 3 anos
[Service]
Type=oneshot
ExecStart=/usr/local/bin/triennial-task.sh
StandardOutput=journal
StandardError=journal
Depois, o timer file em /etc/systemd/system/triennial-task.timer:
[Unit]
Description=Timer para tarefa a cada 3 anos
[Timer]
OnCalendar=*-*-01 02:00:00
Persistent=true
Unit=triennial-task.service
[Install]
WantedBy=timers.target
O timer acima roda todo dia 1º às 2h da manhã. Dentro do script, você verifica se deve executar de fato. A verificação fica no shell script, não no timer.
O script de verificação
Aqui é onde a maioria dos guias falha. Eles mostram o timer mas não explicam como filtrar os anos certos. Meu script usa verificação baseada em epoch para evitar problemas de fuso horário:
👉 Clique no botão abaixo para saber mais sobre o assunto!
#!/bin/bash
set -euo pipefail
TRIENNIAL_INTERVAL=94608000
CURRENT_EPOCH=$(date +%s)
BASE_EPOCH=1735689600
ELAPSED=$((CURRENT_EPOCH - BASE_EPOCH))
CYCLES=$((ELAPSED / TRIENNIAL_INTERVAL))
REMAINDER=$((ELAPSED % TRIENNIAL_INTERVAL))
if [ $REMAINDER -lt 86400 ]; then
echo "Executando tarefa trienal na ciclo $CYCLES"
/usr/local/bin/do-triennial-work.sh
else
echo "Não é hora. Faltam $((86400 - REMAINDER)) segundos para o próximo ciclo."
exit 0
fi
Usei o timestamp 1735689600 que corresponde a 1º de janeiro de 2025 como base. Você deve ajustar para a data que fizer sentido no seu contexto. Se seu sistema começou a operar em 2020, mude o BASE_EPOCH para aquele marco.
Tarefa para 3 anos: edge case que quase destruiu um ambiente de produção
Em 2023, configurei uma tarefa assim para renovação de certificados internos. O timer estava correto, o script também. Mas esqueci de considerar que o primeiro ciclo começaria antes do certificado existir. O sistema tentou renovação em 2023 quando o certificado só seria emitido em 2024. O resultado foi um estado inconsistente onde o certificado antigo ainda estava válido mas o sistema já marcava como "renovado". A solução foi adicionar uma condição de existência de arquivo no início do script. Se o certificado-alvo não existir, o job sai silenciosamente sem marcar nada como concluído. O comando ficou assim no topo do triennial-task.sh:
if [ ! -f /etc/ssl/certs/target-cert.pem ]; then
logger "Certificado ainda não existe, pulando execução"
exit 0
fi
Isso parece óbvio agora mas leva horas pra descobrir quando o log mostra apenas "execução completada com sucesso" e o certificado continua vencido.
Alternativas ao sistema de timer
Se você não tem systemd disponível — ambientes containerizados, máquinas virtuais antigas, ou sistemas embarcados — use um agendador externo. O mais comum é o GNU Anacron, que lida bem com intervalos longos porque roda baseado em dias desde o último execute, não em calendário fixo. Outra opção é usar o próprio cron com uma cadeia de jobs anuais que se desabilitam. Cada ano, um job verifica se passou o ciclo de 3 anos. Se sim, executa e se desativa. Se não, cria o próximo job para o ano seguinte. Funciona mas adiciona complexidade desnecessária se você tiver systemd.
Há também a opção de usar orquestradores como Kubernetes CronJob com schedule anual e lógica de counting nos pods. É viável mas sobrecarrega o cluster para algo que deveria ser simples.
Armadilhas que ninguém menciona
Intervalos longos criam problemas específicos que jobs diários nunca enfrentam. O primeiro é drift de relógio. Se o servidor perde sincronia NTP por semanas, o timer pode disparar fora do ciclo esperado. Sempre verifique o status do serviço ntpd ou chronyd como parte do pré-check do script. O segundo problema é a persistência. Se o servidor desligar no dia exato da execução e o timer não tiver Persistent=true, você perde o ciclo inteiro e só no próximo ano. O timer com persistent resolve isso na maior parte dos casos, mas já vi situações onde o journal do systemd corrompeu e o timer não recuperou o disparo perdido. Mantenha logs externos como fallback.
O terceiro problema é o mais chatto: documentação. Quando alguém herda esse sistema daqui a dois anos, vai achar que é um bug o job não ter rodado no último ano. Deixe um README claro junto com a configuração explicando o ciclo e a data base usada. Se o seu caso for simplesmente "rodar algo a cada 3 anos sem lógica adicional", considere se vale a pena toda essa infraestrutura. Às vezes um script simples chamado manualmente via lembrete no calendar do responsável é mais confiável do que tentar automatizar um intervalo tão longo.