Skip to content

Agendamento cron

Cron a cada 3 dias

0 0 */3 * * é a resposta comum, mas roda nos dias 1, 4, 7, …, 28, 31 de cada mês, então o intervalo reinicia todo mês. Para 3 dias exatos, rode diariamente e teste a contagem de dias: [ $(( $(date +\%s) / 86400 \% 3 )) -eq 0 ] && job.sh.

A cada 3 dias
Expressão0 0 */3 * *
Execuções127 vezes em 2027
OnCalendar do systemd*-*-01/3 00:00:00

Como funciona

Um passo no campo de dia do mês conta a partir do 1 dentro de cada mês: */3 vira 1, 4, 7, 10, 13, 16, 19, 22, 25, 28 e 31. Cada mês recomeça do dia 1, não importa o que aconteceu no fim do anterior.

Isso deixa o espaçamento irregular na virada do mês. Depois do dia 31 de um mês de 31 dias, a próxima execução é no dia 1, um dia depois. Depois do dia 28 de um mês de 30 dias, a próxima é no dia 1, três dias depois. Fevereiro de ano não bissexto vai do dia 28 para 1º de março em um dia. Em 2027 a expressão dispara 127 vezes, e não as 121 ou 122 que um ritmo fixo de 3 dias daria.

Se “de tantos em tantos dias, em datas previsíveis” basta, o */3 é simples e legível. Se o intervalo importa (rotação de credenciais, ciclos de cobrança, APIs com limite), conte os dias desde o epoch, coisa que o cron não faz sozinho mas um teste de uma linha no shell faz.

Explicação campo a campo
CampoValorSignificado
Minuto0minuto 0 (hora cheia)
Hora0hora 0 (0h)
Dia do mês*/3a cada 3 dias, contando do dia 1: 1, 4, 7, …, 31
Mês*todo mês
Dia da semana*todo dia da semana

Próximas execuções

Relógio do agendador

Como no GitHub Actions sem timezone, na Vercel, na Cloudflare e no Kubernetes com timeZone "Etc/UTC".

Próximas execuções, calculadas no seu navegador a partir de agora
ExecuçãoSeu horárioUTC
1Calculando… 
2  
3  
4  
5  

Versões prontas para colar

O mesmo agendamento em cada agendador, com o que muda em cada um.

crontab (Linux, macOS, BSD)

crontab -e
# Calendar days 1, 4, 7, ..., 28, 31 (resets every month):
0 0 */3 * * /usr/local/bin/job.sh

# A true 3-day interval: run daily, act when days-since-epoch % 3 == 0
0 0 * * * [ $(( $(date +\%s) / 86400 \% 3 )) -eq 0 ] && /usr/local/bin/job.sh
  • Dentro de uma linha do crontab o % precisa ser escrito como \%, senão o cron o transforma em quebra de linha. A versão com epoch conta dias UTC inteiros desde 1970, então nunca reinicia na virada do mês.

GitHub Actions

.github/workflows/scheduled.yml
on:
  schedule:
    - cron: '0 0 */3 * *'
      # timezone: 'America/Sao_Paulo'  # opcional; sem ele, UTC
  workflow_dispatch: {}
  • Roda em UTC, a menos que você defina timezone, e só no branch padrão. A documentação do GitHub avisa que execuções agendadas podem atrasar em horários de pico e que, em repositórios públicos, o agendamento é desativado após 60 dias sem atividade.
  • O início de cada hora é o horário de maior fila no GitHub. Se o minuto exato não importa, troque o 0 por outro minuto, como 17, para atrasar menos.
  • Com timezone num fuso que tem horário de verão, um horário que não existe no dia da mudança é adiado para o próximo horário válido (2:30 vira 3:00, segundo a documentação).

CronJob do Kubernetes

cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: scheduled-job
spec:
  schedule: "0 0 */3 * *"
  timeZone: "Etc/UTC"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: job
              image: busybox:1.36
              command: ["/bin/sh", "-c", "date; echo running"]
  • spec.timeZone é estável desde o Kubernetes 1.27; sem ele, vale o fuso do kube-controller-manager. concurrencyPolicy: Forbid pula uma execução se a anterior ainda estiver rodando.

Cron Jobs da Vercel

vercel.json
{
  "crons": [
    { "path": "/api/cron", "schedule": "0 0 */3 * *" }
  ]
}
  • A Vercel sempre usa UTC, não aceita nomes como MON ou JAN e não deixa definir dia do mês e dia da semana ao mesmo tempo.
  • Cabe no plano Hobby, mas lá a precisão é por hora: o disparo pode acontecer em qualquer minuto dentro da hora agendada.

Cloudflare Workers

wrangler.toml
[triggers]
crons = ["0 0 */3 * *"]
  • Cron Triggers rodam em UTC. Mudanças podem levar até 15 minutos para se propagar pela rede da Cloudflare.

node-cron (Node.js)

scheduler.js
import cron from 'node-cron';

// 6 campos: o primeiro (segundos) é opcional
cron.schedule('0 0 0 */3 * *', async () => {
  await runJob();
}, { timezone: 'UTC', noOverlap: true });
  • O agendamento vive dentro do processo Node: se ele cair, nada roda; se houver três réplicas, a tarefa roda três vezes. noOverlap pula um disparo enquanto o anterior não terminou.

Timer do systemd

job.timer + job.service
# /etc/systemd/system/job.timer
[Unit]
Description=Run job.service on days 1, 4, 7, ... of the month

[Timer]
OnCalendar=*-*-01/3 00:00:00
Persistent=true

[Install]
WantedBy=timers.target

# /etc/systemd/system/job.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/job.sh

# systemctl daemon-reload && systemctl enable --now job.timer
  • Confira com systemd-analyze calendar '*-*-01/3 00:00:00', que mostra a forma normalizada e a próxima execução. AccuracySec padrão é 1min, então o disparo pode variar até um minuto.
  • Persistent=true roda a tarefa assim que a máquina liga se o horário passou com ela desligada, algo que o cron não faz.
  • O 01/3 do systemd tem o mesmo reinício mensal do cron. Para uma cadência estrita de 72 horas, use um timer monotônico: OnUnitActiveSec=3d mais OnBootSec=5min para iniciar o ciclo.

Spring @Scheduled e Quartz

Java
@Scheduled(cron = "0 0 0 */3 * *", zone = "UTC")
public void runJob() {
    // ...
}

// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0 1/3 * ?")
  • No Spring, o primeiro dos 6 campos são os segundos; o resto segue o cron comum (0 ou 7 = domingo).
  • No Quartz, um dos campos de dia precisa ser ?, e os dias da semana vão de 1 = domingo a 7 = sábado; por isso o exemplo usa nomes.

Armadilhas

O intervalo na virada do mês

31 de janeiro e 1º de fevereiro rodam os dois, em sequência. De 28 de abril a 1º de maio são três dias. O mesmo reinício afeta */2 (dias 1, 3, …, 31 e de novo 1) e qualquer outro passo de dias.

Acrescentar dia da semana vira E, ou OU

No cronie, um campo de dia do mês que começa com * conta como irrestrito, então 0 0 */3 * 1 significa “a cada três dias E segunda-feira”. Outras implementações tratam */3 como restrito e fazem OU com segunda. Evite combinar os dois.

Agendadores hospedados herdam o mesmo comportamento

GitHub Actions, Kubernetes, Vercel e Cloudflare expandem */3 por mês. Só um timer monotônico (OnUnitActiveSec=3d no systemd) ou um teste de epoch na tarefa dá um período real de 72 horas.

Perguntas frequentes

Qual é a expressão cron para a cada 3 dias?
0 0 */3 * * roda à meia-noite nos dias 1, 4, 7, …, 28 e 31 de cada mês. O intervalo reinicia todo mês, então não é estritamente a cada 3 dias.
Por que meu cron de 3 em 3 dias rodou dois dias seguidos?
Porque o passo recomeça no dia 1. Num mês de 31 dias a tarefa roda no dia 31 e de novo no dia 1.
Como rodar um cron exatamente a cada 3 dias?
Rode diariamente e verifique a contagem de dias desde o epoch Unix: [ $(( $(date +\%s) / 86400 \% 3 )) -eq 0 ] && job.sh. Ou use um timer do systemd com OnUnitActiveSec=3d.
A cada 2 dias tem o mesmo problema?
Tem. */2 no dia do mês roda nos dias ímpares (1, 3, …, 29, 31), então um mês de 31 dias roda no dia 31 e no dia 1.

Revisado em por Arielton Oberek.