Se você administra servidores Debian ou Ubuntu há algum tempo, provavelmente tem uma relação de conforto com o cron. Uma linha no crontab, cinco campos de agendamento e o caminho do script — pronto, resolvido. O cron funciona assim desde os anos 1970, e essa simplicidade é justamente o que o manteve relevante por tanto tempo.

O problema é que “funciona” e “funciona bem em 2026” são coisas diferentes. Quando um job falha silenciosamente às três da manhã, quando você precisa descobrir qual dos vinte crontabs espalhados pelo sistema contém aquela tarefa específica, ou quando o servidor reinicia e simplesmente perde a execução que deveria ter acontecido durante o downtime — nesses momentos o cron mostra que foi projetado para uma época em que as expectativas sobre observabilidade e resiliência eram outras.

Os systemd timers existem desde 2014 e fazem parte de qualquer distribuição baseada em systemd. Não é um pacote extra, não é uma ferramenta de terceiros, mas sim infraestrutura que já está rodando no seu servidor agora mesmo. Este post explica a lógica por trás da mudança, o modelo mental necessário para trabalhar com timers, e o que eles oferecem de concreto em relação ao cron.

Por que mexer no que está funcionando#

O cron em contexto histórico#

O cron nasceu no Unix V7 em 1979. A ideia era elegante na sua simplicidade: um daemon lê uma tabela de agendamentos, verifica a cada minuto se alguma entrada corresponde ao horário atual e, se corresponde, executa o comando associado. Esse modelo sobreviveu praticamente intacto por quase cinco décadas, o que diz muito sobre a solidez do conceito original.

A sintaxe dos cinco campos — minuto, hora, dia do mês, mês e dia da semana — se tornou uma espécie de linguagem franca entre administradores de sistemas. Mesmo quem não trabalha com Linux diariamente já esbarrou em alguma variação dela dentro de CI/CD pipelines, serviços de cloud e ferramentas de automação que adotaram o formato como padrão de fato para expressar agendamentos recorrentes.

Onde o cron começa a mostrar a idade#

O cron faz uma coisa e faz de maneira confiável, mas o ecossistema ao redor dele mudou. Em um servidor moderno com dezenas de tarefas agendadas, algumas limitações se acumulam e começam a pesar.

A mais imediata é a visibilidade. O cron não tem logging próprio — a saída dos jobs vai para o email local do usuário (se o MTA estiver configurado) ou simplesmente se perde. Descobrir se um job rodou, quanto tempo levou e por que falhou exige que você mesmo implemente redirecionamentos de saída, rotação de logs e algum mecanismo de notificação. É trabalho que o administrador acaba repetindo em cada script, de maneiras ligeiramente diferentes, sem nenhuma padronização.

Outro ponto é a dispersão. Jobs podem estar no crontab do root, no crontab de outros usuários, em /etc/crontab, em arquivos dentro de /etc/cron.d/ e nos diretórios cron.daily, cron.hourly e assim por diante. O comando crontab -l mostra apenas o crontab do usuário atual, então montar o mapa completo do que está agendado no sistema exige vasculhar vários lugares. Não existe um equivalente a systemctl list-timers — uma visão consolidada com a próxima execução, a última execução e o status de cada job.

Por fim, o cron não tem noção de dependências nem de estado. Se o servidor estava desligado no horário em que um backup deveria rodar, o cron simplesmente ignora aquela execução — não existe mecanismo nativo para recuperar jobs perdidos. Da mesma forma, se um job depende de a rede estar disponível ou de outro serviço estar ativo, cabe ao script verificar isso por conta própria. São problemas que têm solução, mas a solução fica sempre do lado de fora do cron, na forma de wrappers, locks com flock, verificações manuais e camadas extras de complexidade que vão se acumulando com o tempo.

Como os systemd timers funcionam#

O modelo de dois arquivos: service + timer#

A diferença conceitual mais importante entre o cron e os systemd timers é a separação entre “o que executar” e “quando executar”. No cron, tudo mora na mesma linha — o agendamento e o comando ficam juntos no crontab. Nos systemd timers, essas responsabilidades são divididas em dois arquivos distintos: uma unit de serviço (.service) e uma unit de timer (.timer).

O arquivo .service descreve a tarefa em si. Ele define qual comando executar, com qual usuário, com quais variáveis de ambiente, o que fazer em caso de falha e quais dependências precisam estar satisfeitas antes da execução. É o mesmo tipo de unit file que o systemd usa para gerenciar qualquer outro serviço do sistema — a diferença é que, em vez de rodar continuamente como um daemon, ele usa Type=oneshot para indicar que executa uma vez e termina.

O arquivo .timer cuida exclusivamente do agendamento. Ele define quando o serviço correspondente deve ser disparado, com que precisão, se execuções perdidas devem ser recuperadas e qual aleatoriedade aplicar ao horário de disparo. Por convenção, o systemd associa automaticamente um timer ao service de mesmo nome — se o timer se chama backup.timer, ele vai disparar backup.service sem precisar de nenhuma configuração explícita para vincular os dois.

Essa separação parece burocrática à primeira vista (dois arquivos onde antes bastava uma linha), mas o ganho prático é significativo. O serviço pode ser testado isoladamente a qualquer momento com systemctl start backup.service, sem precisar esperar o horário agendado ou mexer no timer. O timer pode ser ajustado, desabilitado ou substituído sem tocar na lógica de execução. E ambos aparecem no ecossistema padrão do systemd — visíveis no systemctl status, com logs no journalctl, sujeitos às mesmas políticas de recursos e segurança que qualquer outra unit.

Timers de calendário (realtime) vs. timers monotônicos#

Os systemd timers se dividem em duas categorias que refletem duas formas fundamentalmente diferentes de pensar sobre tempo.

Timers de calendário, configurados com a diretiva OnCalendar, disparam em datas e horários absolutos. São o equivalente direto do que o cron faz: “todo dia às 2h da manhã”, “toda segunda-feira às 8h”, “no primeiro dia de cada mês às 3h30”. O horário de referência é o relógio do sistema, e o timer se repete conforme o padrão definido, independentemente de quando o sistema foi ligado ou de quanto tempo se passou desde a última execução.

Timers monotônicos funcionam com durações relativas a algum evento do sistema. As diretivas mais comuns são OnBootSec (tempo após o boot), OnStartupSec (tempo após o systemd ter iniciado), OnUnitActiveSec (tempo após a última ativação do serviço associado) e OnUnitInactiveSec (tempo após a última vez que o serviço ficou inativo). Um timer configurado com OnBootSec=5min e OnUnitActiveSec=1h vai rodar cinco minutos após o boot e depois se repetir a cada hora — medindo sempre a partir do fim da execução anterior, não a partir de um ponto fixo no relógio.

A distinção é relevante na prática. Timers de calendário são a escolha natural para tarefas que precisam acontecer em horários específicos — backups noturnos, rotação de logs, relatórios diários. Timers monotônicos fazem mais sentido para tarefas que dependem de intervalo entre execuções — coleta de métricas, verificações de saúde, limpeza periódica de cache — especialmente em máquinas que não ficam ligadas 24 horas, como laptops e desktops.

O formato OnCalendar e como validá-lo com systemd-analyze#

A sintaxe do OnCalendar segue o padrão DiaDaSemana Ano-Mês-Dia Hora:Minuto:Segundo, onde o dia da semana é opcional e qualquer campo pode usar * para significar “qualquer valor”. Alguns exemplos tornam o formato mais claro do que qualquer explicação abstrata:

  • *-*-* 02:00:00 — todo dia às 2h (equivalente a 0 2 * * * no cron)
  • Mon..Fri *-*-* 08:00:00 — dias úteis às 8h
  • *-*-01 03:30:00 — primeiro dia de cada mês às 3h30
  • *-*-* *:0/15 — a cada 15 minutos

O systemd também aceita atalhos legíveis como daily, hourly, weekly e monthly, que se traduzem internamente para expressões completas no formato acima.

Uma das vantagens concretas sobre a sintaxe do cron é a possibilidade de empilhar múltiplas diretivas OnCalendar no mesmo timer. Um agendamento que roda em horários diferentes durante a semana e no fim de semana — algo que no cron exigiria duas entradas separadas — pode ficar em um único arquivo .timer com duas linhas OnCalendar.

Antes de ativar um timer, o comando systemd-analyze calendar permite validar e visualizar a expressão sem risco. Executando systemd-analyze calendar "Mon..Fri *-*-* 08:00:00", o systemd mostra a forma normalizada da expressão e calcula as próximas datas de disparo. É o tipo de ferramenta que o cron nunca teve — uma forma de testar o agendamento antes de colocá-lo em produção, em vez de esperar para ver se o job roda no horário certo.

O que os timers fazem que o cron não faz#

Logging integrado com journald#

Quando um cron job falha, a investigação costuma começar com uma pergunta desconfortável: “onde foi parar a saída desse script?”. Dependendo de como o job foi escrito, a resposta pode ser um arquivo de log em algum canto do filesystem, o spool de email local, ou simplesmente lugar nenhum. Cada administrador resolve isso do seu jeito — redirecionando stderr para um arquivo, configurando um MTA para entregar emails locais, adicionando chamadas a logger dentro dos scripts — e o resultado é uma colcha de retalhos onde cada job tem sua própria arqueologia de logs.

Com systemd timers, toda a saída padrão e de erro do serviço vai automaticamente para o journal. Sem configuração, sem redirecionamento, sem dependência de MTA. O comando journalctl -u backup.service mostra o histórico completo de execuções daquele serviço com timestamps precisos, e filtros como --since yesterday ou --priority err permitem recortar exatamente o que interessa. A saída do timer e a saída do serviço ficam no mesmo sistema de logging que todo o resto do systemd usa, então correlacionar eventos entre diferentes componentes do sistema deixa de ser um exercício de grep em múltiplos arquivos.

Na prática, isso significa que a pergunta muda de “onde está o log?” para “o que o log diz?” — que é a pergunta que realmente importa quando algo dá errado às três da manhã.

Persistent=true e execuções perdidas#

O cron opera com uma premissa simples: se o sistema está ligado no momento em que um job deveria rodar, ele roda. Se não está, a execução se perde sem aviso e sem recuperação. Para tarefas em servidores que ficam ligados o tempo todo, isso raramente é um problema. Mas para qualquer cenário que envolva reinicializações planejadas, janelas de manutenção, atualizações de kernel que exigem reboot ou máquinas que eventualmente são desligadas, essa é uma lacuna real.

A diretiva Persistent=true no arquivo .timer resolve isso de maneira direta. Quando habilitada, o systemd grava em disco o timestamp da última execução bem-sucedida do timer. No próximo boot, ele compara esse timestamp com o agendamento e, se detecta que uma ou mais execuções foram perdidas durante o período em que o sistema esteve desligado, dispara o serviço assim que possível. O comportamento é determinístico e visível — systemctl list-timers mostra tanto a última execução quanto a próxima, então é trivial confirmar que a recuperação aconteceu.

Replicar esse comportamento com cron exigiria manter arquivos de controle por fora, verificar timestamps no início de cada script e decidir programaticamente se a execução deve prosseguir — lógica que cada administrador teria que implementar, testar e manter por conta própria.

Dependências entre units#

Um padrão comum em cron jobs é começar o script com verificações manuais: testar se a rede está acessível, verificar se um banco de dados está respondendo, checar se um filesystem remoto está montado. Esse tipo de guarda existe porque o cron não tem nenhuma noção do estado do sistema — ele dispara o comando no horário combinado e o que acontece depois é problema do script.

Os systemd timers herdam o sistema de dependências do próprio systemd. As diretivas After=, Requires= e Wants= no arquivo .service permitem declarar que a tarefa só deve ser executada depois que determinadas units estejam ativas. Um backup que depende de um compartilhamento NFS pode declarar After=mnt-backup.mount e Requires=mnt-backup.mount — se o mount point não estiver disponível, o systemd nem tenta executar o serviço, e o motivo fica registrado no journal. Um job que precisa de conectividade pode usar After=network-online.target e Wants=network-online.target em vez de ficar em loop testando se consegue resolver DNS — esse padrão aparece na prática no serviço de túnel SSH reverso que uso para acessar máquinas atrás de NAT.

Isso não elimina toda necessidade de verificação dentro dos scripts — dependências de aplicação, estados de API e condições de negócio continuam sendo responsabilidade da lógica do job. Mas a camada de infraestrutura (rede, mounts, serviços do sistema) passa a ser resolvida de forma declarativa, no mesmo lugar onde o resto da configuração do serviço vive.

Controle de recursos e sandboxing#

Cada cron job roda essencialmente sem limites. Se um script consome toda a memória disponível, satura a CPU ou escreve até encher o disco, o impacto recai sobre todo o sistema. Ferramentas como nice, ionice e ulimit existem, mas precisam ser invocadas explicitamente dentro de cada job, e a granularidade de controle é limitada.

Como os serviços disparados por timers são units regulares do systemd, eles têm acesso ao mesmo conjunto de controles de recursos disponível para qualquer outro serviço. Diretivas como CPUQuota=50%, MemoryMax=512M e IOWeight=100 podem ser declaradas diretamente no arquivo .service, impondo limites via cgroups sem nenhuma modificação no script executado. Um job de backup que não deve consumir mais que metade da CPU e meio giga de RAM expressa isso em duas linhas de configuração, e o systemd garante o cumprimento.

Além de recursos, o systemd oferece diretivas de segurança que permitem restringir o que o serviço pode fazer no sistema. ProtectHome=true impede acesso aos diretórios home dos usuários, ProtectSystem=strict torna o filesystem raiz somente leitura (exceto caminhos explicitamente liberados com ReadWritePaths=), NoNewPrivileges=true bloqueia escalação de privilégios, e PrivateTmp=true dá ao serviço um /tmp isolado. Nenhuma dessas proteções existe no modelo do cron, onde cada job roda com as permissões completas do usuário que o agendou, sem nenhum isolamento adicional.

Da teoria à prática: anatomia de um timer#

Estrutura mínima do .service#

Um arquivo .service para uso com timers precisa de muito pouco. O exemplo abaixo mostra uma unit que executa um script de limpeza de logs antigos:

[Unit]
Description=Limpeza de logs com mais de 30 dias

[Service]
Type=oneshot
ExecStart=/usr/local/bin/limpa-logs.sh

A seção [Unit] contém apenas a descrição, que aparece na saída de systemctl list-timers e systemctl status. A seção [Service] define o tipo como oneshot — indicando que o processo executa, termina e isso é considerado sucesso — e o caminho absoluto do comando em ExecStart.

Não há seção [Install] porque o serviço não é habilitado diretamente. Quem será habilitado é o timer; o serviço existe apenas para ser disparado por ele.

Algumas diretivas opcionais que vale conhecer desde o início:

[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/limpa-logs.sh
TimeoutStartSec=5min

User e Group definem com qual conta o processo roda — sem eles, o padrão é root. TimeoutStartSec estabelece um tempo máximo de execução, depois do qual o systemd encerra o processo e marca o serviço como falho. Para scripts que podem travar esperando um recurso de rede ou um lock, esse limite evita que a tarefa fique pendurada indefinidamente.

O arquivo deve ser salvo em /etc/systemd/system/ com a extensão .service. Seguindo o exemplo: /etc/systemd/system/limpa-logs.service.

Estrutura mínima do .timer#

O timer correspondente precisa ter o mesmo nome-base do serviço. Para limpa-logs.service, o arquivo será limpa-logs.timer:

[Unit]
Description=Executa limpeza de logs diariamente às 3h

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

A seção [Timer] é onde mora a lógica de agendamento. OnCalendar define o horário de disparo — neste caso, todo dia às 3h da manhã. Persistent=true garante que, se o sistema estiver desligado às 3h, a execução aconteça assim que possível após o boot.

A seção [Install] com WantedBy=timers.target é o que permite habilitar o timer com systemctl enable. Quando habilitado, o systemd inclui esse timer no target timers.target, que é ativado automaticamente durante o boot. Diferente do .service, aqui o [Install] é obrigatório — sem ele, o timer funciona se iniciado manualmente, mas não sobrevive a um reboot.

Para um timer monotônico, a seção [Timer] teria uma forma diferente:

[Timer]
OnBootSec=2min
OnUnitActiveSec=1h

Esse timer dispara dois minutos após o boot e depois se repete a cada hora, medindo a partir do fim da execução anterior. Note que Persistent=true não se aplica a timers monotônicos — a diretiva só faz sentido com OnCalendar, onde existe um horário absoluto contra o qual comparar.

O arquivo vai no mesmo diretório: /etc/systemd/system/limpa-logs.timer.

Ativando, verificando e acompanhando#

Com os dois arquivos no lugar, a sequência de ativação é sempre a mesma. Primeiro, o systemd precisa recarregar suas definições para enxergar as novas units:

sudo systemctl daemon-reload

Em seguida, habilitar e iniciar o timer:

sudo systemctl enable limpa-logs.timer
sudo systemctl start limpa-logs.timer

O enable cria o vínculo simbólico para que o timer seja ativado em todo boot. O start o coloca em funcionamento imediatamente, sem esperar pelo próximo reboot. As duas operações podem ser combinadas com a flag --now:

sudo systemctl enable --now limpa-logs.timer

Para confirmar que o timer está ativo e verificar quando será a próxima execução:

systemctl list-timers | grep limpa-logs

A saída mostra a próxima execução prevista, quanto tempo falta, quando foi a última execução e o nome da unit associada — tudo em uma linha. Para uma visão mais detalhada:

systemctl status limpa-logs.timer

Antes de esperar pelo horário agendado, faz sentido testar o serviço isoladamente para garantir que ele funciona:

sudo systemctl start limpa-logs.service

E depois verificar a saída no journal:

journalctl -u limpa-logs.service -n 20

Se o serviço completou sem erros, o timer pode ficar por conta do agendamento. Se algo falhou, o journal vai mostrar exatamente o que aconteceu — sem precisar caçar arquivos de log pelo filesystem, sem depender de email local, sem adivinhar se o script sequer chegou a executar.

Faz total sentido. O logging integrado é uma das maiores vantagens dos timers sobre o cron, e até aqui o post só mencionou o journalctl de passagem sem realmente ensinar a usá-lo. Uma seção dedicada antes da referência rápida fecha esse gap e dá ao leitor a ferramenta prática para acompanhar o que os timers estão fazendo.

Lendo os logs com journalctl#

O journal do systemd é o destino automático de tudo que os serviços escrevem em stdout e stderr. Não precisa de configuração, não depende de redirecionamento no script e não exige um MTA funcionando. Saber navegar nele é o que transforma a vantagem teórica do logging integrado em ganho prático no dia a dia.

Logs de um serviço específico#

O filtro mais comum é por unit. Para ver todas as entradas do serviço de limpeza de logs usado nos exemplos anteriores:

journalctl -u limpa-logs.service

A saída inclui timestamps, o PID do processo e tudo que o script enviou para a saída padrão e de erro. As entradas aparecem em ordem cronológica, da mais antiga para a mais recente.

Para ver apenas as últimas execuções sem percorrer o histórico inteiro, o flag -n limita o número de linhas:

journalctl -u limpa-logs.service -n 30

E para acompanhar a saída em tempo real enquanto o serviço roda — útil quando se está testando com systemctl start — o flag -f funciona como um tail -f:

journalctl -u limpa-logs.service -f

Filtrando por tempo#

Em um servidor com semanas ou meses de histórico, filtrar por período é mais prático do que rolar páginas de log. O journalctl aceita filtros de tempo em linguagem bastante direta:

journalctl -u limpa-logs.service --since today
journalctl -u limpa-logs.service --since yesterday --until today
journalctl -u limpa-logs.service --since "2026-03-20 03:00:00" --until "2026-03-20 03:05:00"

O último exemplo é particularmente útil para inspecionar uma execução específica — sabendo o horário do OnCalendar, basta abrir uma janela de alguns minutos ao redor dele para ver exatamente o que aconteceu.

Identificando falhas#

Quando um serviço termina com código de saída diferente de zero, o systemd o marca como failed. O comando systemctl status mostra essa informação de forma resumida:

systemctl status limpa-logs.service

A saída inclui o estado atual (inactive (dead) para um oneshot que terminou com sucesso, failed se houve erro), o código de saída e as últimas linhas do journal. Para filtrar apenas mensagens de erro e de severidade superior:

journalctl -u limpa-logs.service -p err

Os níveis de prioridade seguem o padrão syslog: emerg, alert, crit, err, warning, notice, info e debug. O filtro -p err mostra tudo de err para cima, o que normalmente é suficiente para chegar direto ao problema.

Correlacionando timer e serviço#

Às vezes o problema não está no serviço em si, mas no disparo do timer — ele pode não estar ativando no horário esperado, ou pode estar ativando duas vezes. Para ver as entradas do timer e do serviço juntas, basta passar as duas units ao journalctl:

journalctl -u limpa-logs.timer -u limpa-logs.service --since today

Isso produz uma timeline intercalada onde dá para ver o momento exato em que o timer disparou e o que o serviço fez em seguida. É o tipo de visibilidade que, com cron, exigiria cruzar logs do syslog com a saída redirecionada do script — quando essa saída existe.

Uma nota sobre persistência dos logs#

Por padrão no Debian e Ubuntu, o journal armazena logs em /var/log/journal/ de forma persistente entre reboots. O espaço em disco é gerenciado automaticamente pelo journald, que por padrão limita o journal a 10% do filesystem ou 4 GB (o que for menor). Para verificar quanto espaço o journal está consumindo:

journalctl --disk-usage

Se o diretório /var/log/journal/ não existir no seu sistema, o journal está rodando em modo volátil — armazenando apenas em memória, com perda total no reboot. Criar o diretório e reiniciar o serviço resolve isso:

sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald

Para a maioria dos servidores essa configuração padrão é suficiente, mas vale confirmar que a persistência está ativa antes de depender do journal como registro histórico das execuções dos seus timers.

Referência rápida: cron → OnCalendar#

A tabela abaixo mapeia os agendamentos mais comuns do cron para seus equivalentes em OnCalendar. Em todos os casos, a expressão pode ser validada antes de usar com systemd-analyze calendar "expressão".

DescriçãocronOnCalendar
A cada minuto* * * * **-*-* *:*:00
A cada 5 minutos*/5 * * * **-*-* *:0/5:00
A cada hora (minuto zero)0 * * * *hourly
Todo dia à meia-noite0 0 * * *daily
Todo dia às 2h3030 2 * * **-*-* 02:30:00
Toda segunda-feira às 8h0 8 * * 1Mon *-*-* 08:00:00
Dias úteis às 18h0 18 * * 1-5Mon..Fri *-*-* 18:00:00
Primeiro dia do mês às 3h0 3 1 * **-*-01 03:00:00
Todo domingo à meia-noite0 0 * * 0Sun *-*-* 00:00:00
A cada 6 horas0 */6 * * *0/6:00:00
Uma vez por semana (domingo, 0h)0 0 * * 0weekly
Uma vez por mês (dia 1, 0h)0 0 1 * *monthly

Os atalhos hourly, daily, weekly e monthly são formas abreviadas que o systemd expande internamente para a expressão completa correspondente. Funcionam bem para agendamentos simples, mas para qualquer variação — um horário diferente de meia-noite, um dia da semana específico — a expressão explícita é necessária.

Uma diferença sutil que vale notar: no cron, o campo de dia da semana usa 0 ou 7 para domingo e números de 1 a 6 para os demais dias. No OnCalendar, os dias são sempre abreviações em inglês de três letras (Mon, Tue, Wed, Thu, Fri, Sat, Sun), e intervalos usam .. em vez de -. A notação é mais legível, mas exige essa adaptação para quem tem a sintaxe do cron na memória muscular.

Quando o cron ainda faz sentido#

Seria desonesto fechar este post sem reconhecer que o cron não se tornou uma ferramenta ruim da noite para o dia. Ele continua sendo uma opção perfeitamente válida em contextos específicos, e migrar tudo para systemd timers por princípio, sem um ganho concreto, é trocar trabalho útil por trabalho burocrático.

Em sistemas onde o agendamento se resume a meia dúzia de tarefas simples — um script de backup, uma rotação de logs, uma limpeza de arquivos temporários — o cron resolve com uma linha o que os timers resolvem com dois arquivos. Se essas tarefas não precisam de logging centralizado, não dependem de outros serviços e rodam em um servidor que fica ligado o tempo todo, o custo-benefício da migração é questionável. O cron funciona, a equipe conhece, o risco de mexer é maior que o ganho.

O cron também tem a vantagem da portabilidade. Ele existe em praticamente qualquer sistema Unix-like — BSDs, distribuições Linux sem systemd como Alpine e Void, ambientes containerizados mínimos onde o systemd não está presente. Se você mantém scripts que precisam rodar em ambientes heterogêneos, o crontab é o denominador comum mais confiável. Systemd timers simplesmente não são uma opção onde o systemd não existe.

Outro caso é o de usuários sem privilégios administrativos. Qualquer usuário pode editar seu próprio crontab com crontab -e sem precisar de sudo. Timers em /etc/systemd/system/ exigem permissões de root. Existem timers de usuário (em ~/.config/systemd/user/), mas eles só rodam enquanto o usuário tem uma sessão ativa — a menos que loginctl enable-linger esteja habilitado para aquela conta, o que por si só requer intervenção do administrador.

O ponto não é escolher um lado. Em um mesmo servidor, cron jobs antigos e estáveis podem coexistir com systemd timers sem nenhum conflito. A recomendação prática é adotar timers para tarefas novas e migrar as existentes conforme a oportunidade aparecer — quando um job precisar de melhor logging, quando uma falha silenciosa causar um problema real, quando o script ganhar uma dependência que justifique declaração explícita. A migração gradual, motivada por necessidade concreta, tende a dar melhores resultados do que uma conversão em massa feita de uma vez.

Se você quer um caso prático para começar, a limpeza de revisões antigas do Snap é um bom candidato para o seu primeiro timer. E se o seu dia a dia inclui um Mac, escrevi um post sobre o launchd — o equivalente nativo do macOS.