Skip to content

Agendamento cron

Cron todo mês

Para rodar um cron uma vez por mês, use @monthly ou 0 0 1 * *, que dispara às 00:00 do dia 1 de todo mês. Qualquer dia de 1 a 28 também dá exatamente 12 execuções por ano.

Todo mês
Expressão0 0 1 * *
Macro@monthly
Execuções12 vezes em 2027
OnCalendar do systemdmonthly

Como funciona

Quem carrega o agendamento é o campo de dia do mês: 1 quer dizer o primeiro dia, e o * no campo de mês deixa isso valer nos doze meses. O crontab(5) e o Kubernetes definem @monthly exatamente assim, e a palavra monthly do systemd é o mesmo horário (*-*-01 00:00:00).

Dá para escolher qualquer dia, mas só de 1 a 28 existem em todo mês. 0 0 15 * * roda no meio do mês, doze vezes por ano. Os dias 29, 30 e 31 pulam sem aviso os meses que não os têm.

Para cobrança, emissão de notas e relatórios mensais, o dia 1 às 00:00 é quando o mês anterior está completo. A tarefa deve calcular “o mês passado”, e não “este mês”, que no dia 1 mal começou.

Explicação campo a campo
CampoValorSignificado
Minuto0minuto 0 (hora cheia)
Hora0hora 0 (0h)
Dia do mês1dia 1
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
# m h dom mon dow  command
0 0 1 * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1
  • O cron usa o fuso horário do servidor. No cronie (Fedora, RHEL), CRON_TZ=America/Sao_Paulo numa linha acima muda o fuso das linhas seguintes.

GitHub Actions

.github/workflows/scheduled.yml
on:
  schedule:
    - cron: '0 0 1 * *'
      # 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 1 * *"
  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 1 * *" }
  ]
}
  • 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 1 * *"]
  • 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 1 * *', 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 monthly, on the 1st at midnight

[Timer]
OnCalendar=monthly
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 'monthly', 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.

Spring @Scheduled e Quartz

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

// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0 1 * ?")
  • 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 dia 31 pula cinco meses

0 0 31 * * roda só em janeiro, março, maio, julho, agosto, outubro e dezembro: sete vezes por ano. 0 0 30 * * pula fevereiro. Se a intenção é o fim do mês, veja a página de último dia do mês.

Fechamento do mês no dia 1

Um relatório que roda no dia 1 precisa consultar o mês anterior. Errar isso gera um relatório vazio no dia 1, e ninguém percebe até alguém perguntar.

Uma execução perdida é um mês de dados faltando

Uma tarefa mensal que falha ou não roda deixa um buraco de um mês inteiro. Alerte em caso de falha e, numa máquina que pode estar desligada, use Persistent=true do systemd ou o anacron.

Perguntas frequentes

Qual é a expressão cron para uma vez por mês?
0 0 1 * *, ou @monthly, roda à meia-noite do dia 1 de todo mês.
Como rodar um cron todo dia 15?
0 0 15 * * roda às 00:00 do dia 15.
Por que meu cron não roda no dia 31 de todo mês?
Só sete meses têm 31 dias, e o cron ignora dias que não existem. Use um dia de 1 a 28, ou a solução do último dia do mês.
O que é @monthly no Spring?
O Spring define @monthly como 0 0 0 1 * *, o mesmo horário com um campo de segundos na frente.

Revisado em por Arielton Oberek.