Cron do Magento 2: o que monitorar, como configurar, como evitar travar
Cron do Magento 2 é a coluna vertebral da loja — sem ele, indexers não atualizam, pedidos não mudam de status, integrações ficam paradas. Este é o conjunto de práticas que evita o ciclo "loja parece bem, mas pedidos estão sumindo".
- Por que ~40% dos bugs reportados de Magento 2 em produção têm raiz no cron.
- Como saber em < 2 minutos se o cron está rodando, atrasado ou travado.
- Os 3 grupos de cron e por que rodar todos no mesmo processo é receita pra travamento.
- Pid-lock, supervisor, e a rotina de purge de
cron_schedule.
Se você opera Magento 2 e ainda não passou pela frase "acho que o cron travou", ou ele está travado agora e você não sabe, ou opera uma loja muito pequena. Cron é a coluna vertebral do Magento 2: sem ele, indexers não atualizam, pedidos não trocam de status, e-mail de boas-vindas não sai, sitemap não regenera, integrações ficam paradas. Este guia é o conjunto de práticas que evita o ciclo de "loja parece bem, mas pedidos estão sumindo".
cron_schedule em loja sem purge, após 1 anoO que o cron do Magento 2 faz
Diferentemente de cron Unix tradicional, o Magento 2 tem seu próprio agendador baseado em banco. O cron Unix invoca php bin/magento cron:run a cada minuto, e o Magento internamente:
- Lê a tabela
cron_schedule(jobs agendados). - Identifica os que estão prontos para executar (
status = 'pending'). - Trava cada um (
status = 'running'), executa, e marca como'success'ou'error'. - Limpa jobs antigos (em teoria, mais sobre isso adiante).
Há 3 grupos distintos:
| Grupo | O que roda | Frequência típica |
|---|---|---|
default | Jobs de negócio: cancelamento de pedido, e-mail, sitemap, currency rate, statistics, customer_notification | A cada 1 min, jobs com periodicidade variável |
index | Reindex incremental (catalog, search, inventory, etc.) | A cada 1 min para jobs em "Update by Schedule" |
consumers | Processa mensagens da fila (queue) — async tasks como atualizar Elasticsearch, processar pedido pesado, etc. | Contínuo (worker pool) |
index bloqueia o default, que bloqueia atualização de status de pedido — e cliente fica com pedido em "processing" eterno.Setup recomendado em produção
Em vez do clássico:
# CRONTAB ANTIGO — RUIM em produção
* * * * * cd /var/www/magento && php bin/magento cron:runUse processos separados:
# CRONTAB MODERNO — 1 entrada por grupo, com lock
* * * * * /usr/bin/flock -n /tmp/m2-cron-default.lock /var/www/magento/bin/magento cron:run --group=default
* * * * * /usr/bin/flock -n /tmp/m2-cron-index.lock /var/www/magento/bin/magento cron:run --group=indexPara os consumers, use supervisor ou systemd — não cron:
# /etc/supervisor/conf.d/magento-consumers.conf
[program:magento-consumer-async-operations]
command=php /var/www/magento/bin/magento queue:consumers:start async.operations.all --max-messages=1000
autostart=true
autorestart=true
user=magento
numprocs=1
stdout_logfile=/var/log/magento/consumer-async-ops.logDiagnóstico rápido: o cron está saudável?
Check 1 — Algum job está rodando há mais de 30 min?
SELECT job_code, status, scheduled_at, executed_at,
TIMESTAMPDIFF(MINUTE, executed_at, NOW()) AS minutes_running
FROM cron_schedule
WHERE status = 'running'
AND executed_at IS NOT NULL
ORDER BY executed_at ASC;Se aparecer job rodando há > 30 min, está travado. Mate o processo (ps aux | grep cron:run, depois kill) e force o status para'error' manualmente — caso contrário o Magento espera ele "terminar" indefinidamente.
Check 2 — Backlog de jobs "missed"
SELECT job_code, COUNT(*) AS missed
FROM cron_schedule
WHERE status = 'missed'
AND scheduled_at > NOW() - INTERVAL 7 DAY
GROUP BY job_code
ORDER BY missed DESC;"missed" significa que o job foi agendado mas não executou na janela permitida — o cron estava ocupado ou parado. Mais que ~50 por dia em qualquer job é sinal vermelho.
Check 3 — Tabela cron_schedule não está inflando?
SELECT COUNT(*) FROM cron_schedule;
-- > 200.000 = purge agressivo está falhandoPurge da tabela cron_schedule
Em teoria, Magento purga jobs antigos automaticamente. Em prática, em loja com muitos jobs e custom modules, esse purge frequentemente nunca acontece — e a tabela cresce até travar o cron.
Configuração em Stores → Configuration → Advanced → System → Cron:
- History Cleanup Every: 60 min (ok)
- Success History Lifetime: 1440 min (1 dia) — pode reduzir para 360 min em loja grande
- Failure History Lifetime: 10080 min (7 dias) — útil para debug
Se mesmo assim a tabela inflar, force purge manual:
DELETE FROM cron_schedule
WHERE created_at < NOW() - INTERVAL 7 DAY
AND status IN ('success', 'error', 'missed');
-- Em loja grande, faça em lotes para não lockar:
DELETE FROM cron_schedule
WHERE created_at < NOW() - INTERVAL 7 DAY
AND status IN ('success', 'error', 'missed')
LIMIT 50000;A parte de queue consumers que cobre 80% dos "pedidos sumindo"
Em Magento 2 moderno (2.4+), boa parte do que parece "cron travado" é na verdade consumer travado. Os consumers processam:
async.operations.all— operações bulk (import/export, mass actions)product_action_attribute.update— update em massa de atributoinventory.indexer.source.item— sincronização de MSIsales.rule.update.coupon.usage— uso de cupom
Veja a fila atual:
SELECT topic_name, COUNT(*) AS pendentes
FROM queue_message qm
JOIN queue_message_status qms ON qm.id = qms.message_id
WHERE qms.status = 4 -- NEW (não processado)
GROUP BY topic_name
HAVING pendentes > 50
ORDER BY pendentes DESC;Mais de 50 mensagens "NEW" em um tópico, e crescendo nos últimos 15 minutos, é consumer travado. Veja o detalhe completo em pedidos travados no Magento 2.
Monitoramento contínuo do cron
O monitoramento mínimo do cron tem 4 sinais — qualquer um disparando é alerta:
- Job rodando há > 30 min em status
running. - Job sem nenhuma execução nos últimos 30 min quando deveria rodar a cada 15 (e.g.
indexer_update_all_views). - Tópico de queue com > 50 NEW e crescendo há 15 min.
cron_schedulecom > 200k linhas — purge não está rodando.
Datadog, New Relic ou um cronjob próprio que rode esses checks e dispare webhook para Discord/Slack atende perfeitamente. A Especialista Loja Virtual tem isso como teste pré-configurado para Magento — basta apontar a credencial.
Erros comuns que matam o cron
- Rodar todos os grupos no mesmo processo cron (sem
--group). - Sem
flock— duas instâncias concorrentes corrompem cron_schedule. - Permissões erradas — cron rodando como root, gerando arquivos que o php-fpm não consegue ler depois.
- Custom module com
cron.xmlmal escrito —scheduleerrado, ou job sem catch de exception, derrubando todo o grupo. - Consumer sem
--max-messages— memória vaza ao longo do tempo, processo morre e supervisor reinicia, perdendo mensagem in-flight.
Checklist final
- Crontab separado por
--groupcomflockativo. - Consumers via supervisor com
--max-messageseautorestart=true. - Purge de
cron_schedulefuncionando (tabela < 200k linhas). - Monitoramento dos 4 sinais ativo, com alerta em Slack/Discord.
- Logs de consumer indo para
/var/log/magento/, com rotação. - Indexers em "Update by Schedule", não "Update on Save" (este último derruba performance).
- Custom
cron.xmlrevisado — todo job com try/catch e logging.
Referências
- Adobe Commerce. Configure and run cron — official guide. experienceleague.adobe.com/en/docs/commerce-operations/configuration-guide/cli/configure-cron-jobs
- Adobe Commerce DevDocs. Manage message queues. developer.adobe.com/commerce/php/development/components/message-queues
- Vinai Kopp. Magento Cron — A Deep Dive. vinaikopp.com
- Mageplaza / Mark Shust. Magento 2 Cron Best Practices — vídeo-aulas e artigos da comunidade.
- Linux man-pages. flock(1) — manage locks from shell scripts. man7.org/linux/man-pages/man1/flock.1.html
Perguntas frequentes
- Posso rodar todos os grupos de cron com um único bin/magento cron:run?
- Pode, mas não em produção. Um job pesado de "index" bloqueia o grupo "default", e pedido fica parado em "processing" eterno. Use --group=default e --group=index em entradas separadas do crontab, com flock para evitar concorrência.
- Por que minha tabela cron_schedule tem 2 milhões de linhas?
- O purge automático parou de funcionar — comum em loja com muitos custom modules. Configure History Lifetime mais agressivo em Stores → Configuration → Advanced → System → Cron e, se preciso, faça purge manual em lotes (DELETE LIMIT 50000).
- Consumer trava sozinho. Como reiniciar automaticamente?
- Use supervisor ou systemd com autorestart=true e --max-messages=1000 no comando do consumer. Após 1000 mensagens, o processo morre por design e o supervisor reinicia. Isso evita vazamento de memória que mata o consumer depois de algumas horas.
Continue lendo
Monitore tudo isso automaticamente
A Especialista Loja Virtual roda navegação real no seu site a cada poucos minutos, alerta no Discord, Slack ou e-mail e mostra screenshot do incidente. Comece grátis.
