Agendamento cron
Cron no último dia do mês
O cron padrão de 5 campos não tem sintaxe para o último dia do mês. Agende 0 0 28-31 * * e deixe o comando rodar só quando amanhã for dia 1; Quartz, Spring, node-cron e Cloudflare aceitam L direto, como em 0 0 L * *.
| Expressão | 0 0 28-31 * * |
|---|---|
| Execuções | 12 vezes em 2027 |
| OnCalendar do systemd | *-*~01 00:00:00 |
Como funciona
Os meses terminam no dia 28, 29, 30 ou 31, e o campo de dia do mês só aceita números fixos. 0 0 31 * * perde cinco meses; 0 0 28-31 * * dispara até quatro vezes no fim de todo mês. O POSIX e o Vixie cron nunca ganharam uma palavra para “último”.
A solução portável é disparar em todo dia candidato e deixar um teste de uma linha decidir: se o dia de amanhã é 01, hoje é o último dia. Numa linha do crontab, o % de date +%d precisa ser escrito \%, senão o cron corta o comando ali.
O Quartz criou o L no campo de dia do mês, e a ideia se espalhou: o CronExpression do Spring, o node-cron 4.x e os Cron Triggers da Cloudflare aceitam, e o systemd tem a própria forma, *-*~01. A tabela de próximas execuções abaixo avalia a forma com L. Para fechamento contábil ou folha de pagamento, lembre que o último dia útil costuma importar mais que o último dia do calendário.
| Campo | Valor | Significado |
|---|---|---|
| Minuto | 0 | minuto 0 (hora cheia) |
| Hora | 0 | hora 0 (0h) |
| Dia do mês | 28-31 | dias 28 a 31 |
| Mês | * | todo mês |
| Dia da semana | * | todo dia da semana |
A tabela descreve a parte portável, 0 0 28-31 * *. As próximas execuções abaixo usam 0 0 L * *, a forma no estilo Quartz, que dispara só no último dia.
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)
# Days 28-31, but only act when tomorrow is the 1st (GNU date):
0 0 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/job.sh
# BSD / macOS date:
0 0 28-31 * * [ "$(date -v+1d +\%d)" = "01" ] && /usr/local/bin/job.sh- Dentro de uma linha do crontab o
%precisa ser escapado como\%; um%sem escape encerra o comando e o resto vai para a entrada padrão dele. O teste roda no máximo quatro vezes por mês e não faz nada em três delas.
GitHub Actions
on:
schedule:
- cron: '0 0 28-31 * *'
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Only on the last day of the month
id: last
run: echo "is_last=$([ "$(date -u -d tomorrow +%d)" = 01 ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
- if: steps.last.outputs.is_last == 'true'
run: ./job.sh- O GitHub usa a sintaxe POSIX do cron, sem
L, então o workflow acorda nos dias 28 a 31 e um passo verifica a data. Aqui não precisa escapar%: essa regra é só do crontab.
CronJob do Kubernetes
apiVersion: batch/v1
kind: CronJob
metadata:
name: month-end
spec:
schedule: "0 0 28-31 * *"
timeZone: "Etc/UTC"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: job
image: alpine:3.20
command: ["/bin/sh", "-c"]
args:
- |
[ "$(date -d @$(( $(date +%s) + 86400 )) +%d)" = "01" ] || exit 0
exec /app/job.sh- A sintaxe de agendamento do Kubernetes não tem
L. O contêiner verifica se amanhã é dia 1 e sai com 0 caso contrário; a forma aritméticadate -d @…funciona no BusyBox (Alpine) e no GNU date.
Cron Jobs da Vercel
{
"crons": [
{ "path": "/api/month-end", "schedule": "0 0 28-31 * *" }
]
}- A Vercel não tem
L. Agende os dias 28 a 31 e faça a função retornar cedo a menos que amanhã (em UTC) seja dia 1. São até quatro execuções por mês, uma por dia, o que o plano Hobby permite (lá com precisão por hora).
Cloudflare Workers
[triggers]
crons = ["0 0 L * *"]- A Cloudflare aceita o
Lno estilo Quartz no campo de dia do mês, então não precisa de gambiarra.LWseria o último dia útil do mês. Os horários são em UTC.
node-cron (Node.js)
import cron from 'node-cron';
// L = last day of the month (node-cron 4.x)
cron.schedule('0 0 0 L * *', async () => {
await runMonthEnd();
}, { timezone: 'UTC' });- O node-cron 4.x aceita
L(eL-3,LW) no campo de dia do mês.
Timer do systemd
# /etc/systemd/system/job.timer
[Unit]
Description=Run job.service on the last day of each month
[Timer]
OnCalendar=*-*~01 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 00:00:00', 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.- O
~significa “contando do fim do mês”:*-*~01é o último dia,*-*~02o penúltimo.
Spring @Scheduled e Quartz
@Scheduled(cron = "0 0 0 L * *", zone = "UTC")
public void monthEnd() { }
// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0 L * ?")- Spring (5.3+) e Quartz aceitam
Lno campo de dia do mês;L-2é dois dias antes do último eLWo último dia útil.
Armadilhas
0 0 31 * * roda sete vezes por ano
Só janeiro, março, maio, julho, agosto, outubro e dezembro têm dia 31. O cron não empurra a execução para o dia mais próximo; simplesmente não dispara.
O % sem escape
date +%d funciona no terminal e quebra em silêncio no crontab, onde % significa quebra de linha. Escreva date +\%d na linha do crontab, ou leve a lógica para um arquivo de script.
Considere o dia 1
Muitas tarefas de fim de mês são, na verdade, tarefas “depois que o mês fecha”. Rodar 0 0 1 * * processando o mês anterior evita o problema do último dia e ainda enxerga o último dia completo.
Perguntas frequentes
- Como rodar um cron no último dia do mês?
- Agende 0 0 28-31 * * e coloque [ "$(date -d tomorrow +\%d)" = "01" ] && antes do comando. Onde o L é aceito (Quartz, Spring, node-cron, Cloudflare), use L no campo de dia do mês.
- O crontab aceita L para o último dia?
- Não. cronie, o cron do Debian e os outros derivados do Vixie cron não aceitam L. Ele vem do Quartz.
- Como fica o último dia do mês no systemd?
- OnCalendar=*-*~01 00:00:00. O til conta os dias a partir do fim do mês.
- Como rodar no último dia útil do mês?
- Use LW no Quartz, no Spring, no node-cron ou na Cloudflare. No cron puro, rode nos dias 26 a 31 em dias de semana e verifique no script que não sobra outro dia útil no mês; feriados exigem uma lista à parte.
Revisado em por Arielton Oberek.