Os cinco campos
Um crontab é uma tabela de agendamentos lida pelo daemon cron (na prática, o crond). Cada linha tem cinco campos de tempo seguidos do comando a executar. Os campos são, na ordem: minuto, hora, dia-do-mês, mês e dia-da-semana. O daemon acorda a cada minuto, compara o relógio com cada linha e roda o comando quando os campos casam. A especificação base é a do POSIX (The Open Group), mas quase todo Linux roda uma variante de Vixie cron (hoje mantida como cronie), que acrescenta extensões úteis, os atalhos @daily, os nomes de mês e o valor 7 para domingo.
┌───────────── minute (0-59)
│ ┌─────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌─────── month (1-12)
│ │ │ │ ┌───── day of week (0-7, 0/7 = Sun)
│ │ │ │ │
* * * * * command to run| Campo | Intervalo | Valores/nomes especiais |
|---|---|---|
| Minuto | 0–59 | — |
| Hora | 0–23 | — |
| Dia do mês | 1–31 | — |
| Mês | 1–12 | nomes JAN–DEC |
| Dia da semana | 0–7 | 0 e 7 = domingo; nomes SUN–SAT |
O dia-da-semana é a pegadinha silenciosa. No POSIX estrito, o intervalo é 0–6 com 0 = domingo. O Vixie cron estende para 0–7 e faz **tanto 0 quanto 7 significarem domingo**, uma cortesia para quem espera a semana terminar no 7. Isso importa quando você escreve 1-5 (segunda a sexta) ou 6,0 (fim de semana). Os nomes de mês e de dia (JAN, SUN) também são extensão do Vixie, não do POSIX; num sistema minimalista, prefira os números. O Gerador de Cron trata 7 como domingo e valida os intervalos campo a campo, então é um bom lugar para conferir se sun e 0 produzem o mesmo agendamento.
Os operadores e o passo
Cada campo aceita quatro construções. O asterisco casa todos os valores; a vírgula lista valores avulsos; o hífen define um intervalo inclusivo; e a barra aplica um passo, pulando de N em N dentro de um intervalo (ou de um asterisco, que equivale ao intervalo cheio do campo). O POSIX define só *, , e -; o passo / é extensão do Vixie, outra razão para não presumir que toda expressão roda em todo lugar.
| Operador | Significado | Exemplo | Resultado |
|---|---|---|---|
| * | todos os valores do campo | * * * * * | a cada minuto |
| , | lista de valores | 0 0,12 * * * | às 00:00 e às 12:00 |
| - | intervalo inclusivo | 0 9-17 * * * | de hora em hora, das 09 às 17 |
| / | passo dentro do intervalo | */15 * * * * | nos minutos 0, 15, 30, 45 |
**Exemplo trabalhado.** Tome */15 9-17 * * 1-5, a cada 15 minutos, das 9h às 17h, de segunda a sexta. Aqui só o dia-da-semana está restrito (o dia-do-mês é *), então não há armadilha: é um E limpo entre os campos de tempo e os dias úteis. Partindo de uma quinta-feira, 09/07/2026, às 16:07, os próximos disparos são: 16:15, 16:30, 16:45, 17:00, 17:15, 17:30 e 17:45 (a hora 17 entra no intervalo 9-17). Depois de 17:45, a janela fecha; o próximo é sexta 10/07 às 09:00. O fim de semana é pulado, então após sexta o job só volta na segunda 13/07 às 09:00.
A armadilha do OU: dia-do-mês e dia-da-semana
Aqui está a regra que quebra a intuição de todo mundo. Você lê a expressão da esquerda para a direita e supõe que todos os campos precisam casar ao mesmo tempo, um grande E. É verdade para minuto, hora e mês. Mas dia-do-mês e dia-da-semana têm um tratamento especial: quando **os dois** estão restritos (nenhum é *), o cron dispara quando **qualquer um dos dois** casa. É um OU, não um E. O crontab(5) do Vixie diz, literalmente: "If both fields are restricted (i.e., do not contain the '*' character), the command will be run when either field matches the current time." O POSIX descreve a mesma semântica com outras palavras.
Não há como expressar "sexta-feira 13" num único campo do cron POSIX, justamente por causa do OU. A solução idiomática é agendar para todo dia 13, 0 0 13 * *, e testar o dia-da-semana dentro do próprio comando, por exemplo [ "$(date +\%u)" = "5" ] && /caminho/script. Como só o dia-do-mês fica restrito (o dia-da-semana volta a ser *), a regra do OU não se ativa: o cron dispara todo dia 13 e o test decide se aquele 13 é sexta. A regra prática é simples: se você precisa que os dois campos de dia atuem juntos como E, deixe um deles em * e mova a outra condição para um if no script.
Atalhos: @daily, @reboot e companhia
No lugar dos cinco campos, o Vixie cron aceita apelidos começados por @. Eles não fazem parte do POSIX, mas estão em praticamente todo Linux e deixam a intenção óbvia. Cada apelido corresponde a uma expressão de cinco campos, exceto o @reboot, que não tem equivalente porque significa "uma vez, quando o cron sobe".
| Atalho | Equivale a | Quando roda |
|---|---|---|
| @yearly / @annually | 0 0 1 1 * | 1º de janeiro, 00:00 |
| @monthly | 0 0 1 * * | dia 1 de cada mês, 00:00 |
| @weekly | 0 0 * * 0 | todo domingo, 00:00 |
| @daily / @midnight | 0 0 * * * | todo dia, 00:00 |
| @hourly | 0 * * * * | toda hora, no minuto 0 |
| @reboot | (sem equivalente) | uma vez, ao iniciar o cron |
Dois detalhes: @midnight é sinônimo de @daily no código do cronie, ainda que o manual atual só documente @daily; e @reboot roda quando o **daemon** sobe (tipicamente no boot), não a cada login, se o serviço reiniciar sozinho, o job dispara de novo. Duas dúvidas que sempre voltam, como o cron lida com o horário de verão e o que exatamente conta como "reboot", merecem um pouco mais de espaço.
Cron e horário de verão (DST): jobs pulados e repetidos
O cron roda no fuso do sistema onde o daemon vive, e as transições de horário de verão mexem no relógio local. O manual do cron(8) descreve um tratamento especial para saltos de menos de 3 horas (o caso do DST). Na primavera, quando o relógio avança e uma hora "some", os jobs que teriam rodado na hora pulada são executados logo após a virada. No outono, quando o relógio recua e uma hora se repete, os jobs que caem na hora repetida não são executados de novo. Isso vale para agendamentos de horário específico; jobs com curinga (ex.: * * * * *) rodam normalmente sobre o novo horário.
Mudanças de mais de 3 horas são tratadas como correção de relógio, e o novo horário passa a valer imediatamente. A lição operacional: para tarefas sensíveis a fuso, agende o daemon em UTC ou evite a janela de transição (algo entre 1h e 3h da manhã, dependendo do país). Se precisa raciocinar sobre esses saltos, o guia de fusos horários, UTC e horário de verão detalha por que "02:30" pode não existir ou existir duas vezes, e o conversor de fuso horário ajuda a alinhar o horário do servidor com o do seu público.
O que exatamente @reboot executa
@reboot dispara uma única vez, quando o serviço do cron é iniciado. Na maioria dos sistemas isso coincide com o boot da máquina, mas não é garantido: se você reiniciar apenas o daemon (por exemplo, systemctl restart cron), os jobs @reboot rodam de novo, sem a máquina ter reiniciado. Também não há hora associada, ele não "espera" um horário; executa assim que o cron sobe e lê o crontab.
Por isso @reboot é frágil como mecanismo de inicialização de serviços. Se o objetivo é subir um processo junto com o sistema, um serviço do systemd (com dependências, reinício automático e ordem de boot) é mais robusto. Reserve @reboot para tarefas idempotentes e baratas, limpar um diretório temporário, reconstruir um cache, em que rodar de novo após um restart do daemon não causa dano.
Nem todo "cron" é POSIX: Quartz e Spring
Boa parte das expressões que circulam na internet não roda num crontab de Linux, são do Quartz, o agendador do mundo Java, ou do formato usado pelo Spring. A pista mais visível é a contagem de campos: o Quartz usa seis (segundos, minuto, hora, dia-do-mês, mês, dia-da-semana) mais um sétimo opcional de ano; o Spring usa seis, com o segundo na frente. Se você contar seis números onde esperava cinco, provavelmente está diante de uma expressão com campo de segundos, colá-la num crontab do sistema faz o cron reclamar ou interpretar tudo errado.
Cron POSIX / Vixie (Linux)
- 5 campos: minuto, hora, dia-do-mês, mês, dia-da-semana.
- Dia-da-semana 0–7 (0 e 7 = domingo).
- Operadores *, ,, - e (no Vixie) /.
- Dia-do-mês e dia-da-semana restritos = OU.
- Sem segundos, sem ?, L, W, #.
Quartz / Spring (Java)
- 6 campos (segundos na frente) + ano opcional no Quartz.
- Dia-da-semana 1–7 (1 = domingo, 7 = sábado).
- ? = "sem valor específico" num dos campos de dia.
- L = último dia; W = dia útil mais próximo.
- # = n-ésimo dia da semana (6#3 = 3ª sexta).
O Quartz resolve a armadilha do OU de um jeito diferente: ele **proíbe** especificar dia-do-mês e dia-da-semana ao mesmo tempo. Você é obrigado a pôr ? em um dos dois, deixando explícito qual dia manda. A documentação afirma que "you must currently use the '?' character in one of these fields". É por isso que expressões do Quartz costumam ter um ? onde o cron do Linux teria um *, e por isso 6#3 (terceira sexta do mês), que o Quartz expressa num campo só, exige um teste no script quando você está no cron POSIX. Ao migrar entre os dois mundos, reescreva a expressão a partir da intenção, não copie os símbolos: além do campo de segundos e do ?, a numeração do dia-da-semana muda (0 = domingo no Linux, 1 = domingo no Quartz).
Receitas prontas
A maioria dos agendamentos do dia a dia cabe em meia dúzia de padrões. A tabela abaixo lista expressões de cinco campos verificadas, com o que cada uma faz. Todas evitam a armadilha do OU: sempre que os dois campos de dia poderiam colidir, um deles fica em *.
| Expressão | O que faz |
|---|---|
| 0 0 * * * | Todo dia à meia-noite (igual a @daily). |
| */15 * * * * | A cada 15 minutos (nos minutos 0, 15, 30, 45). |
| 0 9 * * 1-5 | Às 09:00, de segunda a sexta. |
| 0 */6 * * * | A cada 6 horas (00:00, 06:00, 12:00, 18:00). |
| 0 0 1 * * | Dia 1 de cada mês, 00:00 (igual a @monthly). |
| 0 0 * * 0 | Todo domingo à meia-noite (igual a @weekly). |
| 23 0-23/2 * * * | No minuto 23 de cada hora par (00:23, 02:23, …, 22:23). |
| 30 3 1 1 * | Às 03:30 de 1º de janeiro, todo ano. |
Uma última recomendação de higiene: agende sempre com um plano B para o horário. Se o servidor estiver desligado no instante exato, o cron comum simplesmente perde o disparo (o anacron cobre parte disso em desktops). E instrumente o job, um cron que roda mas falha em silêncio é pior que um que não roda. Um padrão barato é o job fazer uma requisição a um endpoint de "pulso" no fim; se o pulso não chega, você recebe alerta. Para interpretar o que o servidor devolve nesse pulso, o guia de códigos de status HTTP essenciais separa o 2xx que confirma sucesso do 5xx que denuncia falha. E quando o job grava carimbos de tempo Unix nos logs, o conversor de timestamp transforma aqueles números em datas legíveis na hora de depurar.
Perguntas frequentes
Por que meu cron de "sexta-feira 13" roda em outros dias?
*/5 significa "a cada 5 minutos a partir de agora"?
0 ou 7 para domingo, qual usar?
Por que uma expressão de 6 campos não funciona no meu crontab?
O cron respeita o horário de verão?
Cron são cinco campos e quatro operadores, mas dois detalhes derrubam quem confia na intuição: */n conta a partir do zero do campo, não de "agora", e dia-do-mês com dia-da-semana viram um OU quando os dois estão restritos. Domine esses dois pontos, saiba distinguir o cron POSIX das extensões do Quartz e valide sempre pelos próximos disparos antes de confiar num agendamento.