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:
- Uma unidade
.timer. Ela decide quando algo é executado. - 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.
- Uma unidade de serviço.
- 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 definirUnit=manualmente. Type=oneshotserve 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=oneshotnão vai rastrear isso corretamente; nesse caso, useType=forkingouRemainAfterExit=true. A maioria dos scripts que você mesmo escreve não vai precisar disso.Nice=19eIOSchedulingClass=idledizem ao kernel que esse job é de baixa prioridade, para que não dispute recursos com coisas mais urgentes.ProtectSystem=stricttorna todo o sistema de arquivos somente leitura para esse serviço, exceto pelos caminhos que você permite explicitamente comReadWritePaths=. 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.servicevai mostrá-lo comoactive (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)
- 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. - Habilitar o serviço em vez do timer. Isso roda o job uma vez, imediatamente, e depois nunca mais no agendamento.
- Confundir
OnUnitActiveSec=eOnUnitInactiveSec=. Isso afeta principalmente serviços de longa duração, como na seção 3.2. - 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. - Esquecer a seção
[Install]. SemWantedBy=timers.target, não há nada parasystemctl enableconectar, então o timer não inicia no boot. - Não verificar o status depois que algo dá errado. Um job
oneshotque failed aparece comofailed, e o timer ainda dispara no ciclo seguinte. Mas você não vai notar a menos que olhe os logs ou configureOnFailure=. - Esperar que
OnCalendar=dispare no segundo exato. O padrãoAccuracySec=1minsignifica que o job pode atrasar até um minuto. Isso é normal, não é bug. - Confiar no arquivo de unidade só porque
daemon-reloadnão reclamou. O systemd pode ignorar silenciosamente uma diretiva com erro de digitação em vez de acusar o erro. Rodesystemd-analyze verifydepois das edições. - 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.timerpara a lista completa de diretivasman systemd.timepara a sintaxe de calendário e de intervalos de tempoman systemd.servicepara opções de serviço comoType=e diretivas de sandboxing



