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.
| Expressão | 0 0 1 * * |
|---|---|
| Macro | @monthly |
| Execuções | 12 vezes em 2027 |
| OnCalendar do systemd | monthly |
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.
| Campo | Valor | Significado |
|---|---|---|
| Minuto | 0 | minuto 0 (hora cheia) |
| Hora | 0 | hora 0 (0h) |
| Dia do mês | 1 | dia 1 |
| Mês | * | todo mês |
| Dia da semana | * | todo dia da semana |
Próximas execuções
Como no GitHub Actions sem timezone, na Vercel, na Cloudflare e no Kubernetes com timeZone "Etc/UTC".
| Execução | Seu horário | UTC |
|---|---|---|
| 1 | Calculando… | |
| 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)
# 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_Paulonuma linha acima muda o fuso das linhas seguintes.
GitHub Actions
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
0por outro minuto, como17, para atrasar menos. - Com
timezonenum 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
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: Forbidpula uma execução se a anterior ainda estiver rodando.
Cron Jobs da Vercel
{
"crons": [
{ "path": "/api/cron", "schedule": "0 0 1 * *" }
]
}- A Vercel sempre usa UTC, não aceita nomes como
MONouJANe 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
[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)
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.
noOverlappula um disparo enquanto o anterior não terminou.
Timer do systemd
# /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.AccuracySecpadrão é 1min, então o disparo pode variar até um minuto. Persistent=trueroda 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
@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.