Se você usa Linux há algum tempo, provavelmente conhece os cron jobs. Você os usa para executar um script toda noite às 2 da manhã, ou a cada 5 minutos. A maioria dos sistemas Linux modernos (Debian, Ubuntu, Fedora, Arch, RHEL) usa o systemd como sistema de init, e o systemd tem sua própria forma de agendar tarefas: chamamos essas tarefas de timer units.

Systemd Timers fazem o mesmo trabalho que o cron, mas conectados ao restante do systemd. Isso significa que você ganha logs adequados, tratamento de dependências e controle de recursos sem esforço extra.

Um timer do systemd sempre vem em duas partes:

  1. Uma unidade .timer. Ela decide quando algo é executado.
  2. Uma unidade .service. Ela decide o que é executado.

O único trabalho do timer é iniciar o serviço na hora certa. Depois que você entende essa divisão, o resto se encaixa.

Teste agora mesmo, antes de escrever qualquer coisa: execute systemctl list-timers na sua máquina. Você quase certamente vai ver timers já em execução, como apt-daily.timer, logrotate.timer ou man-db.timer.

$ systemctl list-timers
NEXT                                 LEFT LAST                              PASSED UNIT                           ACTIVATES
Tue 2026-08-25 11:30:00 UTC      3min 40s Tue 2026-08-25 11:20:00 UTC     6min ago sysstat-collect.timer          sysstat-collect.service
Tue 2026-08-25 12:21:48 UTC         55min -                                      - fwupd-refresh.timer            fwupd-refresh.service
Tue 2026-08-25 19:37:38 UTC            8h Tue 2026-08-25 11:06:29 UTC            - motd-news.timer                motd-news.service
Tue 2026-08-25 21:35:55 UTC           10h -                                      - apt-daily.timer                apt-daily.service
Wed 2026-08-26 00:00:00 UTC           12h -                                      - dpkg-db-backup.timer           dpkg-db-backup.service
Wed 2026-08-26 00:00:00 UTC           12h -                                      - sysstat-rotate.timer           sysstat-rotate.service
Wed 2026-08-26 00:07:00 UTC           12h -                                      - sysstat-summary.timer          sysstat-summary.service
Wed 2026-08-26 00:45:21 UTC           13h -                                      - logrotate.timer                logrotate.service
Wed 2026-08-26 06:29:14 UTC           19h -                                      - apt-daily-upgrade.timer        apt-daily-upgrade.service
Wed 2026-08-26 11:06:17 UTC           23h -                                      - man-db.timer                   man-db.service
Wed 2026-08-26 11:12:00 UTC           23h Tue 2026-08-25 11:12:00 UTC    14min ago update-notifier-download.timer update-notifier-download.service
Wed 2026-08-26 11:22:00 UTC           23h Tue 2026-08-25 11:22:00 UTC 4min 19s ago systemd-tmpfiles-clean.timer   systemd-tmpfiles-clean.service
Sun 2026-08-30 03:10:01 UTC        4 days -                                      - xfs_scrub_all.timer            xfs_scrub_all.service
Sun 2026-08-30 03:10:34 UTC        4 days -                                      - e2scrub_all.timer              e2scrub_all.service
Mon 2026-08-31 00:55:31 UTC        5 days -                                      - fstrim.timer                   fstrim.service
Sun 2026-09-06 01:25:59 UTC 1 week 4 days -                                      - update-notifier-motd.timer     update-notifier-motd.service
16 timers listed.
Pass --all to see loaded but inactive timers, too.

Esses não são especiais. São unidades de timer comuns, construídas do mesmo jeito que você está prestes a construir as suas. Você pode inspecionar qualquer uma delas com systemctl cat man-db.timer para ver como um timer de produção real é escrito.

Observe que os timers dependem do systemd rodando como PID 1.

$ ps -p 1
PID TTY          TIME CMD
1 ?        00:00:06 systemd

Dentro da maioria dos contêineres Docker, de alguns chroots mínimos e de certas configurações do WSL, o systemd não está rodando; portanto, os timers (e o próprio systemctl) não funcionam lá sem configuração extra. Se um timer que você criou parece não fazer nada silenciosamente, verifique ps -p 1 primeiro, antes de sair depurando os arquivos de unidade.

1. A configuração básica

Digamos que você queira fazer backup de uma pasta todos os dias às 3:30 da manhã. Você precisa de dois arquivos.

  1. Uma unidade de serviço.
  2. Uma unidade de timer.

Primeiro, crie a unidade de serviço:

sudo nano /etc/systemd/system/backup.service

Adicione as seguintes linhas:

[Unit]
Description=Back up home directory
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Nice=19
IOSchedulingClass=idle
ProtectSystem=strict
ReadWritePaths=/var/backups

Depois, crie a unidade de timer:

sudo nano /etc/systemd/system/backup.timer

Adicione estas linhas:

[Unit]
Description=Run backup.service daily at 3:30 AM
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target

Algumas coisas para notar:

  • O timer e o serviço compartilham o mesmo nome (backup). O systemd usa essa correspondência de nomes para vinculá-los. Você só precisa apontar o timer para outro serviço se definir Unit= manualmente.
  • Type=oneshot serve para a maioria das tarefas agendadas que rodam até o fim sozinhas. Se o seu script cria um processo em segundo plano ou se daemoniza, Type=oneshot não vai rastrear isso corretamente; nesse caso, use Type=forking ou RemainAfterExit=true. A maioria dos scripts que você mesmo escreve não vai precisar disso.
  • Nice=19 e IOSchedulingClass=idle dizem ao kernel que esse job é de baixa prioridade, para que não dispute recursos com coisas mais urgentes. ProtectSystem=strict torna todo o sistema de arquivos somente leitura para esse serviço, exceto pelos caminhos que você permite explicitamente com ReadWritePaths=. Nada disso é obrigatório para o timer funcionar, mas é um endurecimento pequeno e barato que vale a pena depois da fase "será que isso roda?".
  • Você habilita e inicia o timer, não o serviço. O serviço você inicia direto apenas quando quer testá-lo.
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer

2. Duas formas de agendar: tempo de calendário e tempo relativo

É aqui que muitos iniciantes se perdem, então vamos com calma.

2.1 Timers de calendário (OnCalendar=)

Eles disparam em um ponto fixo no relógio, bem parecido com o cron. A sintaxe vem de systemd.time(7):

OnCalendar=*-*-* 03:30:00         # every day at 3:30:00 AM
OnCalendar=Mon,Fri *-*-* 09:00:00 # every Monday and Friday at 9 AM
OnCalendar=*-*-01 00:00:00        # the first day of every month, at midnight
OnCalendar=weekly                 # shortcut for Monday at 00:00
OnCalendar=daily                  # shortcut for every day at midnight
OnCalendar=hourly                 # shortcut for the top of every hour
OnCalendar=*:0/15                 # every 15 minutes

Aqui vai um truque que facilita a vida. Você pode testar qualquer uma dessas expressões sem esperar o disparo:

$ systemd-analyze calendar "Mon,Fri *-*-* 09:00:00"
Original form: Mon,Fri *-*-* 09:00:00
Normalized form: Mon,Fri *-*-* 09:00:00
Next elapse: Mon 2026-08-17 09:00:00 IST
(in UTC): Mon 2026-08-17 03:30:00 UTC
From now: 1 day 18h left

Rode isso antes de habilitar um timer novo. Isso evita o erro clássico de escrever uma linha OnCalendar que parece certa, mas dispara na hora errada.

Existe também uma segunda verificação complementar:

systemd-analyze verify

Isso carrega um arquivo de unidade do mesmo jeito que o systemd faria e imprime problemas de sintaxe, sem iniciar nada.

sudo systemd-analyze verify /etc/systemd/system/backup.timer

Isso é importante porque o systemd costuma ser tolerante com erros de digitação em arquivos de unidade. Uma diretiva escrita errado pode ser ignorada silenciosamente, sem erro alto, e o timer simplesmente não faz o que você esperava. Crie o hábito de rodar systemd-analyze verify depois de cada edição, sempre junto com daemon-reload.

Observe que os timers do systemd não são exatos por padrão. Existe uma configuração oculta AccuracySec= com padrão de 1 minuto, então o systemd pode deslocar o início do job em até um minuto para qualquer lado. Ele faz isso de propósito, para agrupar wake-ups próximos e economizar energia (isso importa bastante em laptops).

Se você realmente precisa de precisão de second-level, defina AccuracySec=1us na seção [Timer]. Para quase tudo que você vai construir como iniciante, o padrão é suficiente.

2.2 Timers relativos (OnBootSec=, OnUnitActiveSec= e amigos)

Esses não ligam para o relógio de parede. Em vez disso, disparam depois de um tempo definido a partir de algum evento. O cron não tem equivalente para isso, então vale a pena aprender bem.

Aqui tem uma armadilha que pega muita gente. OnUnitActiveSec= e OnUnitInactiveSec= parecem quase a mesma coisa, mas não são. Para um job oneshot curto, a diferença quase não importa, já que o job começa e para quase instantaneamente. Mas para um serviço de longa duração, os dois se comportam de forma bem diferente. OnUnitActiveSec=1h conta a partir do momento em que o serviço iniciou. OnUnitInactiveSec=1h conta a partir do momento em que ele parou. Misturar os dois faz a sua programação se desviar silenciosamente.

Um padrão comum: rodar um script de limpeza 10 minutos depois do boot e, depois, a cada 6 hours.

[Timer]
OnBootSec=10min
OnUnitActiveSec=6h

3. Recuperando execuções perdidas com Persistent=

Digamos que seu laptop esteja dormindo às 3:30 da manhã quando backup.timer deveria disparar. Nada acontece. O job é simplesmente pulado, a menos que você adicione esta linha:

[Timer]
Persistent=true

Com Persistent=true, o systemd lembra a última vez em que o timer realmente disparou (ele guarda isso em /var/lib/systemd/timers/). Se a sua máquina perdeu uma execução agendada porque estava desligada ou dormindo, o job roda uma vez logo depois do próximo boot para recuperar o atraso. Isso funciona de forma parecida com a antiga ferramenta anacron. Por isso, Persistent=true é quase sempre uma boa ideia em laptops e desktops, enquanto servidores que ficam ligados o tempo todo muitas vezes não precisam.

4. Distribuindo a carga com RandomizedDelaySec=

Imagine o seguinte: você gerencia cinquenta servidores, e todos eles rodam OnCalendar=daily à meia-noite. Os cinquenta atingem o seu servidor backup no mesmo segundo. Isso é uma manada trovejante, e ela pode derrubar as coisas.

A correção é assim:

[Timer]
OnCalendar=daily
RandomizedDelaySec=1800

Cada máquina escolhe o próprio atraso aleatório, de até 30 minutos, antes de rodar de verdade. A carga se espalha naturalmente. Se você quiser que cada máquina escolha o mesmo atraso toda vez, em vez de um novo aleatório a cada execução, adicione também FixedRandomDelay=true (disponível a partir do systemd 247).

5. Os comandos de timers do systemd que você vai usar todo dia

# List all active timers and when each one fires next
systemctl list-timers
# Include timers that are disabled or haven't triggered yet
systemctl list-timers --all
# Check a specific timer's status
systemctl status backup.timer
# Check how the last run of the service went
systemctl status backup.service
# Read the logs from the service the timer triggered
journalctl -u backup.service
# Trigger the service right now, without waiting for the timer
sudo systemctl start backup.service
# Enable at boot and start immediately
sudo systemctl enable --now backup.timer
# Stop and disable it
sudo systemctl disable --now backup.timer
# Run this after you edit any unit file
sudo systemctl daemon-reload

systemctl list-timers dá um resumo limpo em uma linha:

NEXT                        LEFT     LAST                         PASSED  UNIT           ACTIVATES
Mon 2026-08-17 03:30:00 IST 12h left Sun 2026-08-16 03:30:00 IST  9h ago  backup.timer   backup.service

Você vê imediatamente quando o timer roda, quanto tempo falta, quando rodou por último e qual serviço ele aciona. Com o cron, você normalmente precisaria confiar em crontab -l e torcer. Isso é um avanço real.

Uma ressalva sobre logs: journalctl -u backup.service assume que o journal está mantendo histórico. Algumas imagens mínimas ou embarcadas de distribuições vêm com Storage=volatile em /etc/systemd/journald.conf, o que significa que os logs vivem apenas na memória e somem no reboot. Se os seus logs parecem desaparecer, essa configuração é a primeira coisa a verificar.

6. E se um job ainda estiver rodando quando o timer disparar de novo?

Digamos que backup.service normalmente termine em 5 minutos, mas hoje, por qualquer motivo, ele ainda está rodando 6 hours depois, quando o timer deve disparar de novo. O que acontece?

Nada de ruim, mas talvez não o que você esperaria: o systemd não inicia uma segunda instância sobreposta. Ativar uma unidade que já está ativa é efetivamente um no-op. O próximo disparo agendado do timer passa silenciosamente, e o serviço simplesmente continua rodando até terminar.

Para a maioria das tarefas, isso é exatamente a rede de segurança que você quer, já que impede pile-ups descontroladas. Mas também significa que, se um job travar, você pode perder um dia inteiro de execuções sem que nenhum erro apareça.

Duas conclusões práticas:

  • Se uma tarefa não pode se sobrepor de jeito nenhum e não pode travar silenciosamente, combine-a com TimeoutStartSec= em [Service] (veja a lista de armadilhas abaixo) para que uma execução travada seja morta em vez de bloquear todas as execuções futuras.
  • Se você desconfiar que um job está travado, systemctl status backup.service vai mostrá-lo como active (running) por muito mais tempo do que o esperado. Esse é o seu sinal.

7. Timers rápidos de uso único sem escrever arquivos de unidade

Às vezes você só quer rodar algo uma vez, em breve, sem criar um par .timer e .service. O comando systemd-run faz isso em tempo real:

# Run a command 10 minutes from now
sudo systemd-run --on-active="10min" /path/to/script.sh
# Run at a specific date and time
sudo systemd-run --on-calendar="2026-08-20 22:00:00" /path/to/script.sh
# Run 5 minutes after every boot, as your own user
systemd-run --user --on-boot="5min" /path/to/script.sh

systemd-run cria um timer e um serviço transitórios em segundo plano (você pode vê-los com systemctl list-timers) e os remove automaticamente no reboot.

Isso é ótimo para testar uma ideia de agendamento ou para uma tarefa necessária uma única vez. Para qualquer coisa que precise sobreviver ao reboot e rodar para sempre, volte a escrever arquivos de unidade como mostrado antes.

8. Executando timers sem root

Até aqui, tudo assumiu unidades de sistema em /etc/systemd/system/. Você também pode configurar timers como usuário comum usando a flag --user. Sem root!

mkdir -p ~/.config/systemd/user
# put backup.service and backup.timer in that folder
systemctl --user daemon-reload
systemctl --user enable --now backup.timer

Fique atento: timers de usuário normalmente só rodam enquanto a sua sessão está ativa. Se você quiser que continuem rodando depois do logout, habilite o lingering (isso exige root, mesmo para a sua própria conta):

sudo loginctl enable-linger $USER

9. systemd Timers vs. cron: quando usar qual

O caso a favor dos timers:

Eles dão logs reais por meio de journalctl, então você não precisa mais de hacks MAILTO= nem de redirecionamento manual de saída. Você também pode declarar dependências de verdade, por exemplo After=network-online.target ou Requires=postgresql.service, algo que o cron simplesmente não faz.

Você pode isolar o serviço com opções como ProtectSystem=strict ou PrivateTmp=true. E ainda tem teste integrado (systemd-analyze calendar), uma visão geral limpa (list-timers), além do comportamento de catch-up e do jitter tratados nativamente, em vez de improvisados com scripts.

O caso a favor do cron:

Ele roda em quase todo lugar, inclusive em sistemas sem systemd, como Alpine com OpenRC, os BSDs ou um contêiner mínimo. Uma entrada de uma-linha crontab é muito mais simples do que escrever dois arquivos de unidade, especialmente para uma tarefa sem dependências.

E se a sua equipe já tem anos de ferramentas baseadas em cron, migrar tudo pode não valer o esforço.

Qual escolher?

Em um sistema com systemd, os timers geralmente são o melhor padrão, principalmente quando logging, recuperação de execuções perdidas ou ordenação de dependências importam para você.

Para um script rápido, portátil e sem dependências, o cron ainda resolve bem. Pense nisso menos como uma regra e mais como uma decisão baseada no seu ambiente.

9.1 Convertendo uma linha crontab para OnCalendar

Se você está migrando jobs cron existentes, este é o mapeamento que vai usar repetidas vezes. Os campos do cron vão minute hour day month weekday; OnCalendar= reordena isso como weekday year-month-day hour:minute:second.

Note a última linha: @reboot não tem equivalente de calendário porque não está preso a um horário de relógio de parede. Esse é exatamente o tipo de coisa para a qual OnBootSec= existe, e é um bom exemplo de timers do systemd fazendo algo que o cron, estruturalmente, não consegue.

10. Recebendo alertas quando um job falha

Essa é a pergunta que quase todo mundo faz depois do primeiro timer funcionar: "como eu descubro se ele parou de funcionar?"

Com o cron, a resposta honesta costumava ser: "você não descobre, até alguém notar que os backup sumiram."

O systemd tem uma resposta real: OnFailure=.

Adicione isso ao seu serviço, apontando para um segundo serviço pequeno que envia uma notificação:

# /etc/systemd/system/backup.service
[Unit]
Description=Back up home directory
OnFailure=alert-on-failure@%n.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

E o serviço de alerta:

# /etc/systemd/system/[email protected]
[Unit]
Description=Send a failure alert for %i
[Service]
Type=oneshot
ExecStart=/usr/local/bin/notify-failure.sh "%i"

%n expande para o nome da unidade que failed, e %i no serviço de alerta recebe esse nome. notify-failure.sh pode ser tão simples quanto uma chamada curl para um webhook ou uma chamada para mail. O ponto não é o mecanismo de notificação; é que OnFailure= dispara automaticamente, apenas em falha, sem polling e sem um timer extra vigiando o primeiro.

11. Exemplo completo: limpeza semanal de logs

# /etc/systemd/system/logcleanup.service
[Unit]
Description=Purge logs older than 30 days
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/find /var/log/myapp -name "*.log" -mtime +30 -delete
# /etc/systemd/system/logcleanup.timer
[Unit]
Description=Weekly log cleanup
[Timer]
OnCalendar=Sun *-*-* 04:00:00
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target

Agora coloque para funcionar e confira se está realmente agendado:

sudo systemctl daemon-reload
sudo systemctl enable --now logcleanup.timer
systemd-analyze calendar "Sun *-*-* 04:00:00"   # confirm the schedule is right
systemctl list-timers logcleanup.timer          # confirm systemd knows about it
sudo systemctl start logcleanup.service         # test it right away
journalctl -u logcleanup.service -n 20          # check that it worked

12. Erros que iniciantes cometem (e como evitar)

  1. Pular o daemon-reload. Depois de editar um arquivo de unidade, o systemd ainda tem a versão antiga em cache na memória. Sempre recarregue.
  2. Habilitar o serviço em vez do timer. Isso roda o job uma vez, imediatamente, e depois nunca mais no agendamento.
  3. Confundir OnUnitActiveSec= e OnUnitInactiveSec=. Isso afeta principalmente serviços de longa duração, como na seção 3.2.
  4. Deixar de fora o Persistent=true. Em um laptop, isso significa que seu job diário pula dias silenciosamente sempre que a máquina está dormindo no horário agendado.
  5. Esquecer a seção [Install]. Sem WantedBy=timers.target, não há nada para systemctl enable conectar, então o timer não inicia no boot.
  6. Não verificar o status depois que algo dá errado. Um job oneshot que failed aparece como failed, e o timer ainda dispara no ciclo seguinte. Mas você não vai notar a menos que olhe os logs ou configure OnFailure=.
  7. Esperar que OnCalendar= dispare no segundo exato. O padrão AccuracySec=1min significa que o job pode atrasar até um minuto. Isso é normal, não é bug.
  8. Confiar no arquivo de unidade só porque daemon-reload não reclamou. O systemd pode ignorar silenciosamente uma diretiva com erro de digitação em vez de acusar o erro. Rode systemd-analyze verify depois das edições.
  9. Achar que um job travado vai ser morto sozinho. Por padrão, não vai. Se um script pode travar (um mount de rede obsoleto, esperando entrada que nunca chega), adicione TimeoutStartSec= em [Service] para que o systemd o mate em vez de deixá-lo bloquear todas as execuções futuras, como na seção 7.

13. Referência rápida para timers do systemd

Para mais detalhes sobre timers do systemd, leia as páginas de manual.

  • man systemd.timer para a lista completa de diretivas
  • man systemd.time para a sintaxe de calendário e de intervalos de tempo
  • man systemd.service para opções de serviço como Type= e diretivas de sandboxing