DesenvolvedorWordPress WhatsApp

WP-Cron: como agendar tarefas no WordPress (do jeito certo)

O WP-Cron deixa você executar tarefas recorrentes — limpar dados, sincronizar APIs, enviar e-mails. Mas ele não é o cron do sistema, e ignorar essa diferença é a origem da maioria dos bugs de agendamento. Entenda como funciona de verdade e como deixá-lo confiável.

Precisa automatizar tarefas no WordPress?

Atualizado em 19/06/2026

Quase todo site WordPress precisa, em algum momento, rodar uma tarefa "sozinho": limpar transients expirados, verificar atualizações, publicar um post agendado, sincronizar estoque com um ERP, enviar um resumo por e-mail. O WordPress oferece para isso o WP-Cron — um agendador de tarefas. O problema é que ele tem um comportamento que confunde quase todo mundo no começo e que, mal compreendido, gera tarefas que "não rodam no horário", rodam em duplicidade ou simplesmente nunca rodam. Este guia explica como o WP-Cron funciona por dentro, como agendar corretamente e como torná-lo confiável em produção.

1. O ponto mais importante: WP-Cron não é o cron do sistema

Se você guardar uma única coisa deste artigo, que seja esta: o WP-Cron não tem um relógio próprio. Ele não é um daemon rodando no servidor que dispara tarefas em horários exatos. O nome engana.

O que acontece de fato é o seguinte: a cada requisição ao site, perto do fim do carregamento, o WordPress dispara uma chamada HTTP de loopback (uma requisição não-bloqueante para si mesmo) para o arquivo wp-cron.php. Esse arquivo verifica a fila de eventos agendados e, se encontrar algum cujo horário já passou, executa o hook correspondente. Em outras palavras:

Isso tem duas consequências práticas que você precisa internalizar antes de escrever qualquer linha de código:

Site sem tráfego não executa tarefas no horário. Se ninguém acessa o site entre 2h e 6h, uma tarefa agendada para as 3h só vai disparar na primeira visita após esse horário. Num site de baixo movimento, atrasos de horas são normais.

Site com muito tráfego pode disparar a checagem com frequência demais. Em picos, cada visita tenta abrir a chamada de loopback, o que adiciona sobrecarga. É por isso que, em produção séria, o padrão é desligar o WP-Cron e controlar o disparo pelo cron real.

2. Agendar uma tarefa: recorrente e evento único

Há duas funções centrais. Para algo que se repete em intervalos, use wp_schedule_event(); para disparar uma vez no futuro, use wp_schedule_single_event():

<?php
// Recorrente: ( timestamp inicial, recorrência, hook, args opcionais )
wp_schedule_event( time(), 'hourly', 'dwp_sincroniza_estoque' );

// Evento único: ( timestamp futuro, hook, args opcionais )
wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'dwp_envia_email_boas_vindas', array( $user_id ) );

Os timestamps são sempre Unix, em UTC. Constantes como HOUR_IN_SECONDS, DAY_IN_SECONDS e WEEK_IN_SECONDS existem no núcleo justamente para deixar esses cálculos legíveis.

O callback precisa estar ganchado SEMPRE. Agendar só coloca o hook na fila. Quem executa o trabalho é o add_action() ligado àquele hook — e ele precisa estar registrado em toda requisição, não apenas no momento em que você agendou. Esse é, de longe, o erro nº 1 de quem usa WP-Cron pela primeira vez.

3. As recorrências padrão e como criar a sua

O WordPress já traz quatro intervalos prontos. A partir do WordPress 5.4, o weekly também faz parte do núcleo:

RecorrênciaIntervaloUso típico
hourly1 horaSincronizar dados, checar filas
twicedaily12 horasVerificações duas vezes ao dia
daily24 horasRelatórios, limpeza diária
weekly7 dias (WP 5.4+)Resumos semanais, manutenção

Quando esses intervalos não bastam (por exemplo, "a cada 15 minutos"), você cria a sua própria recorrência com o filtro cron_schedules. Lembre-se: um filtro sempre retorna o valor — aqui, o array de recorrências com a sua adicionada:

<?php
add_filter( 'cron_schedules', 'dwp_intervalos_personalizados' );

function dwp_intervalos_personalizados( $schedules ) {
    $schedules['cada_15_min'] = array(
        'interval' => 15 * MINUTE_IN_SECONDS, // em segundos
        'display'  => __( 'A cada 15 minutos', 'meu-plugin' ),
    );
    return $schedules; // nunca esqueça de retornar o array completo
}

Depois disso, você pode usar 'cada_15_min' como recorrência em wp_schedule_event(), exatamente como usaria 'hourly'.

4. Agendar na ativação e evitar duplicação

Onde agendar? O lugar certo é o register_activation_hook() do plugin, que roda uma única vez, quando o plugin é ativado. Mas mesmo ali você deve verificar se o evento já não existe, com wp_next_scheduled() — caso contrário, reativar o plugin (ou rodar o agendamento por engano em outro ponto) empilha eventos duplicados, e a tarefa passa a rodar várias vezes.

<?php
// Roda UMA vez, na ativação do plugin
register_activation_hook( __FILE__, 'dwp_agendar_eventos' );

function dwp_agendar_eventos() {
    // Só agenda se ainda não houver esse evento na fila
    if ( ! wp_next_scheduled( 'dwp_sincroniza_estoque' ) ) {
        wp_schedule_event( time(), 'hourly', 'dwp_sincroniza_estoque' );
    }
}

// IMPORTANTE: o gancho do callback fica FORA da ativação,
// no carregamento normal do plugin, para rodar em toda requisição.
add_action( 'dwp_sincroniza_estoque', 'dwp_executa_sincronizacao' );

function dwp_executa_sincronizacao() {
    // ... o trabalho de verdade acontece aqui.
    // Mantenha idempotente: rodar de novo não deve duplicar efeitos.
}

5. Limpar o agendamento na desativação

Ao desativar o plugin, você precisa remover os eventos da fila — senão o WordPress continuará tentando disparar um hook cujo código não está mais carregado. Use wp_clear_scheduled_hook(), que remove todos os eventos agendados para aquele hook:

<?php
register_deactivation_hook( __FILE__, 'dwp_limpar_eventos' );

function dwp_limpar_eventos() {
    wp_clear_scheduled_hook( 'dwp_sincroniza_estoque' );
}

Não confunda desativar com desinstalar. Limpe os agendamentos na desativação (com register_deactivation_hook). Se o seu evento foi agendado com argumentos (o array $args), o WordPress trata cada combinação de argumentos como um agendamento distinto — wp_clear_scheduled_hook() remove todos eles para aquele hook, o que normalmente é o que você quer.

6. Confiabilidade e performance: desligue o WP-Cron em produção

Esta é a parte que separa o código amador do profissional. Como vimos, depender das visitas é frágil (tarefas atrasam) e custoso (cada visita tenta um loopback). A solução padrão de mercado é desabilitar o WP-Cron e disparar o WordPress por um cron de verdade.

Primeiro, no wp-config.php, antes do comentário "That's all, stop editing":

<?php
// Impede o WordPress de disparar o cron a partir das visitas
define( 'DISABLE_WP_CRON', true );

Com isso, a fila de eventos continua existindo e sendo gravada normalmente — você apenas tira o gatilho "por visita". Agora, configure o cron do sistema para chamar o WordPress em intervalos fixos. A forma recomendada é via WP-CLI, porque ele roda os eventos vencidos no contexto PHP correto, sem depender de HTTP:

# crontab -e  (no servidor)
# Roda os eventos vencidos a cada 5 minutos via WP-CLI (recomendado)
*/5 * * * * cd /var/www/seusite/html && wp cron event run --due-now >/dev/null 2>&1

# Alternativa sem WP-CLI: chamar wp-cron.php por HTTP
# */5 * * * * wget -q -O - "https://seusite.com.br/wp-cron.php?doing_wp_cron" >/dev/null 2>&1
# (ou, com curl:)
# */5 * * * * curl -s "https://seusite.com.br/wp-cron.php?doing_wp_cron" >/dev/null 2>&1

A diferença entre as duas abordagens importa. Chamar wp-cron.php por wget/curl funciona, mas passa por toda a pilha HTTP do site e está sujeito a cache, firewall e timeouts da requisição web. O wp cron event run --due-now executa em PHP-CLI, sem o teto de tempo de uma requisição web e sem passar por proxy/cache — é mais robusto para tarefas que demoram.

7. Depurar o que está agendado

Você não precisa adivinhar o que está na fila. Com o WP-CLI, wp cron event list mostra todos os eventos, o hook, a recorrência e quando cada um dispara — essencial para diagnosticar duplicações e atrasos:

# Lista todos os eventos agendados (hook, próxima execução, recorrência)
wp cron event list

# Lista as recorrências disponíveis (inclui as suas, via cron_schedules)
wp cron schedule list

# Dispara manualmente um hook específico, ignorando o horário
wp cron event run dwp_sincroniza_estoque

Resumo das funções principais

FunçãoPara quê
wp_schedule_event()Agendar um evento recorrente (com recorrência)
wp_schedule_single_event()Agendar um evento único no futuro
wp_next_scheduled()Verificar o próximo disparo de um hook (evita duplicar)
wp_clear_scheduled_hook()Remover todos os eventos de um hook (na desativação)
wp_unschedule_event()Remover um evento específico (timestamp + hook + args)

Armadilhas comuns (e como evitar)

SintomaCausa provávelCorreção
Tarefa nunca executaadd_action do hook só registrado na ativaçãoRegistre o add_action no carregamento normal, sempre
Tarefa atrasa horasSite de baixo tráfego (WP-Cron por visita)Desligue o WP-Cron e use cron do sistema
Tarefa roda em duplicidadeAgendou sem checar wp_next_scheduled()Verifique antes de wp_schedule_event()
Recorrência customizada ignoradaFiltro cron_schedules não retornou o arraySempre dê return $schedules
Tarefa morre no meioEstourou o max_execution_timeDivida em lotes e reagende; torne idempotente
Horário erradoConfusão de fuso (tudo é UTC internamente)Calcule timestamps em UTC; cuidado com strtotime()

O WP-Cron é um agendador poderoso desde que você respeite a regra de ouro — ele depende de um gatilho. Para tarefas críticas (cobranças, sincronização de estoque, backups), nunca confie no disparo por visita: desligue o WP-Cron e use o cron do sistema. E lembre-se de que o agendamento vive dentro de um plugin que registra seus hooks em toda requisição.

Precisa de tarefas agendadas confiáveis no seu WordPress?

Configuro WP-Cron, cron real do servidor e rotinas idempotentes para sincronização, relatórios e integrações. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

O WP-Cron é o mesmo que o cron do sistema (crontab)?

Não, e essa é a confusão que mais causa problema. O WP-Cron é um agendador simulado dentro do WordPress: ele não tem um relógio próprio rodando no servidor. Em vez disso, a cada requisição o WordPress dispara uma chamada de loopback para o arquivo wp-cron.php, que verifica se há tarefas vencidas e as executa. O cron do sistema (o crontab do Linux) é o oposto: um daemon que roda comandos em horários exatos, independentemente de haver visitas. O ideal, em produção, é desligar o WP-Cron e deixar o cron do sistema disparar o WordPress.

Por que minha tarefa agendada não roda no horário certo?

Porque o WP-Cron só é verificado quando alguém acessa o site. Se o seu blog não recebe visitas entre 2h e 6h da manhã, uma tarefa agendada para as 3h só vai rodar quando chegar a primeira visita depois disso — pode ser às 8h. Em sites de baixo tráfego o atraso é a regra, não a exceção. A solução é usar o cron real do sistema chamando wp-cron.php (ou, melhor, o WP-CLI) em intervalos fixos.

Por que o callback precisa estar ganchado fora do momento de agendar?

Porque agendar um evento (wp_schedule_event) só grava na fila qual hook deve disparar e quando. Quem executa de fato é o add_action() ligado àquele hook. Se você registrar o add_action apenas dentro do bloco de ativação, no momento em que o cron disparar (em outra requisição, dias depois) o callback não estará pendurado e nada acontece. O add_action tem que rodar em toda requisição, no carregamento normal do plugin.

Como evito que a tarefa seja agendada várias vezes?

Sempre verifique com wp_next_scheduled( 'seu_hook' ) antes de chamar wp_schedule_event(). Essa função devolve o timestamp do próximo evento agendado para aquele hook, ou false se não houver nenhum. Agendar dentro do register_activation_hook() (que roda uma única vez) somado a essa verificação evita filas com o mesmo evento repetido, que fariam a tarefa rodar em duplicidade.

Em qual fuso horário o WP-Cron trabalha?

Internamente, tudo é UTC. Os timestamps que você passa para wp_schedule_event() e wp_schedule_single_event() devem ser timestamps Unix (sempre em UTC). Para agendar "às 3h da manhã no horário de Brasília", calcule o timestamp considerando o fuso — usar time() já devolve UTC corretamente, mas montar horários "humanos" com strtotime() exige cuidado com o fuso configurado no PHP e no WordPress. Não confie no fuso do painel para a lógica do agendamento.

O que acontece se minha tarefa de cron demorar muito?

Como o WP-Cron roda dentro de uma requisição PHP, ele está sujeito ao max_execution_time. Uma tarefa longa (processar milhares de registros, gerar um relatório pesado) pode estourar o tempo e morrer no meio, deixando o trabalho pela metade. A correção é dividir o trabalho em lotes, reagendando o próximo lote como evento único, e tornar a tarefa idempotente — segura para rodar de novo sem duplicar efeitos. Veja também otimização de performance.

Referências oficiais

  1. WordPress.org — WP-Cron (Plugin Handbook)
  2. WordPress.org — Scheduling WP-Cron Events
  3. WordPress.org — Hooking WP-Cron Into the System Task Scheduler
  4. WordPress.org — wp_schedule_event() (Code Reference)
  5. WordPress.org — wp cron (WP-CLI Commands)
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui