Tarefas em segundo plano não estão sendo executadas? Conserte filas, cron e workers rapidamente
Tarefas em segundo plano que não rodam podem quebrar e-mails, faturamento e sincronizações. Aprenda a identificar se o problema é cron, filas, workers, retries ou idempotência e como consertar.

Como é quando tarefas em segundo plano param de funcionar
Quando tarefas em segundo plano não estão rodando, o app pode parecer bem à primeira vista. Páginas carregam, usuários navegam e formulários são enviados. Mas o trabalho “depois” nunca acontece.
As pessoas notam efeitos colaterais ausentes: o e-mail de boas-vindas não chega, uma importação CSV fica em “processando” para sempre, um webhook de pagamento é recebido mas o pedido não é atualizado, ou uma sincronização noturna para de atualizar números. Chamados de suporte soam vagos (“está preso”) porque nada falha visivelmente.
Isso é especialmente comum em protótipos gerados por IA. Eles costumam funcionar localmente porque uma máquina faz tudo: serve o app web, dispara agendamentos e roda workers. Em produção essas partes ficam distribuídas entre serviços, containers ou máquinas. Uma peça faltando quebra a cadeia.
A maioria das falhas cai em quatro categorias:
- Agendamento: cron/temporizadores não disparam, fusos horários erram, ou o agendamento roda no ambiente errado.
- Deploy do worker: o worker não está rodando, não alcança a fila, ou está apontando para a fila errada.
- Retries: jobs falham e entram em retry infinito, ou falham uma vez e somem sem alertas.
- Idempotência: um job roda duas vezes e corrompe o estado, então é desativado ou fica sempre em erro.
Antes de mexer no código, reúna alguns fatos. Isso economiza horas de tentativa e erro e deixa mais claro onde a pipeline quebra:
- O timestamp quando a ação do usuário aconteceu (ou quando o agendamento deveria ter disparado)
- O nome/tipo do job e qualquer ID de job que seu sistema registre
- Logs do worker naquele período (não apenas logs web)
- Métricas da fila: pendentes, em progresso, falhados
- O último horário de execução bem-sucedida do mesmo job
Se você herdou um protótipo de ferramentas como Replit, Cursor ou Bolt, esse “pacote de evidências” é o que times como o FixMyMess usam para identificar se falta um scheduler, um worker está morto, ou se um job está falhando silenciosamente.
Cron, filas e workers: o modelo mental simples
Pense no trabalho em segundo plano como um sistema de entregas. Algo decide quando o trabalho deve acontecer, algo guarda o trabalho, e algo de fato o executa.
Cron (ou um scheduler) é o relógio. Ele dispara uma tarefa numa agenda como “a cada 5 minutos” ou “toda segunda às 9h”. Uma fila é a caixa de correio. Ela guarda jobs até que um worker esteja pronto. Um worker é o carteiro. Ele puxa jobs da fila e executa o código.
Jobs pontuais e recorrentes começam de formas diferentes. Um job pontual geralmente é criado pelo seu app (por exemplo, “redimensionar esta imagem após upload”). Um job recorrente costuma ser criado pelo cron (por exemplo, “enviar relatórios semanais”). Se você usa uma fila, ambos acabam no mesmo lugar: um registro de job aguardando processamento.
Em produção, o estado precisa viver em algo compartilhado e durável: uma tabela de banco para execuções agendadas, Redis para mensagens de fila, ou um serviço de fila gerenciado. Se um protótipo depende de estado em memória, ele pode funcionar localmente e falhar assim que houver mais de um servidor ou após um reinício.
O desenvolvimento local esconde problemas porque tudo roda num processo só. Em produção, são processos separados. Se o cron não estiver instalado, se o worker não estiver implantado, ou se o worker não alcançar o Redis ou o banco, você tem o sintoma clássico: tarefas em segundo plano não rodam mesmo que o app pareça “saudável” na superfície.
Triage rápido: onde a pipeline está quebrando?
Não chute. Descubra qual parte da pipeline está falhando: os jobs não estão sendo criados, não estão sendo pegos, ou estão rodando e falhando?
1) Os jobs estão sendo criados?
Comece com a divisão mais simples: “nenhum job existe” vs “o job existe mas nada acontece”. Procure um registro de job na sua tabela/loja de filas, ou qualquer linha de log que confirme que um job foi enfileirado.
Se você não vê nada, geralmente é agendamento. Cron/temporizadores podem não estar rodando em produção, podem estar rodando no ambiente errado, ou não ter as permissões corretas. Um erro comum em protótipos é depender de um scheduler local (seu laptop) sem implantar um processo de scheduler real.
2) Jobs são criados mas nunca processados?
Se jobs existem mas ficam “queued”, provavelmente há um worker que não está processando. Confirme se um worker realmente está rodando no ambiente de produção (não apenas durante o deploy) e ouvindo o mesmo nome de fila que seu app usa.
Verifique também conectividade e credenciais. Workers precisam acessar o backend da fila (Redis/SQS/banco) e ter as mesmas variáveis de ambiente que o app. Uma variável diferente pode fazer o worker parecer “saudável” enquanto nunca vê os jobs.
3) Jobs rodam mas falham e entram em retry infinito?
Se você vê o número de tentativas aumentar, o agendamento provavelmente está ok. Agora é execução. Abra o erro mais recente e procure a causa repetida (erros de autenticação, segredos faltando, timeouts). Retries devem ter backoff e um número máximo de tentativas.
Se um job toca em dinheiro, e-mail ou dados do usuário, assuma que ele pode rodar duas vezes. Falta de idempotência é como um “está falhando” vira “está falhando alto”, com cobranças duplicadas ou e-mails repetidos.
Consertando problemas de agendamento (cron e timers)
Primeiro verifique o óbvio: algo está realmente disparando o job, e está disparando quando você pensa que está?
Confirme o agendamento
Expressões cron podem parecer corretas e ainda assim estarem erradas. Fusos horários são a armadilha mais comum. Seu laptop pode estar no horário local enquanto a produção roda em UTC. Isso transforma “todo dia às 9h” em “todo dia à 1h” ou “fora da janela de testes”.
Também cheque como sua plataforma interpreta o agendamento. Alguns sistemas usam cron de 5 campos, outros 6 campos (com segundos). Um campo extra desloca tudo.
Checagens rápidas que pegam a maioria dos erros:
- Logue os próximos 5 horários de execução do seu scheduler e confirme que batem com o esperado.
- Confirme a configuração de fuso horário no config do app e no ambiente de hospedagem.
- Valide a string cron contra a biblioteca de scheduler que você realmente usa.
- Garanta que o job está habilitado em produção (feature flags e variáveis de ambiente costumam diferir).
- Confirme que apenas uma instância do scheduler está ativa (ou você pode gerar duplicados).
Torne o scheduler seguro em produção
Um scheduler que roda no seu laptop não passa a rodar automaticamente após o deploy. Em protótipos, é comum colocar o scheduler dentro do processo do servidor web. Então ele para sempre que o dyno web reinicia, escala para zero ou hiberna.
Execuções perdidas são outra falha silenciosa. Se o app reinicia às 9:01, o job das 9:00 nunca dispara a menos que você projete para isso. Para tarefas importantes, considere uma abordagem de catch-up: na inicialização, verifique o que deveria ter rodado e enfileire o que faltou.
Por fim, trate sobreposições. Se um job pode levar mais tempo que seu intervalo, você precisa de um lock ou de uma regra “não sobrepor”. Caso contrário verá envios duplicados, cobranças duplicadas ou atualizações concorrentes.
Consertando deploy e conectividade do worker
Se o agendamento parece correto mas jobs ainda não são processados, olhe o worker. Uma fila só anda quando um processo worker está online, conectado e consegue rodar continuamente.
Confirme que existe um processo worker no mesmo ambiente onde o app está implantado (não apenas no seu laptop). Você quer evidência de que ele inicia, permanece ativo e aponta para a mesma fila que o app envia jobs. Cheque a lista de processos ou o painel de serviços, e então verifique logs por uma linha de inicialização como "escutando" ou "conectado".
Lacunas de deploy que aparecem frequentemente em protótipos gerados por IA:
- O worker nunca foi implantado (somente o web app foi).
- O comando do worker está errado (ele roda um script de dev, ou sai imediatamente).
- Variáveis de ambiente faltando no worker (URL da fila, credenciais, NODE_ENV).
- O worker aponta para um nome/região de fila diferente do app web.
- O worker cai e reinicia em loop (geralmente por limites de memória ou dependências faltando).
Problemas de conectividade soam parecido: jobs se acumulam e nada os consome. Causas comuns são host errado, portas bloqueadas, credenciais expiradas ou um serviço de fila acessível apenas de uma rede privada onde o worker não está.
A concorrência importa também. Poucos workers demais = processamento lento; muitos demais = timeouts, limites de taxa ou estouros de memória. Ponto de partida simples: 1–2 workers com baixa concorrência, e aumente apenas quando ver memória estável e tempo de job previsível.
Exemplo: uma startup implanta um job “enviar e-mails de fatura”. O app web enfileira bem, mas o worker roda sem as credenciais da fila em produção, então inicia e sai. Corrigir o ambiente do worker (e adicionar um log claro “conectado à fila”) torna o problema óbvio na próxima vez.
Retries, backoff e tratamento de falhas que não se espalha
Às vezes a reclamação “tarefas em segundo plano não estão rodando” significa: o job está rodando, falhando instantaneamente e retryando tão rápido que nunca avança. Regras de retry bem projetadas mantêm o sistema calmo e os logs legíveis.
Separe falhas em dois grupos:
- Erros transitórios geralmente somem: timeouts, limites de taxa, locks de banco temporários, falhas de rede breves. Esses devem retryar.
- Erros permanentes não melhoram com o tempo: dados faltando, API key inválida, “usuário não encontrado”, erros de validação ou bug de código que lança sempre na mesma linha. Esses devem falhar rápido e ir para um estado “morto” para revisão.
Backoff evita que um problema pequeno vire uma tempestade. Um default sensato é um número pequeno de tentativas (por exemplo, 3–10), com atrasos que crescem a cada tentativa. Adicione um pouco de jitter randômico para que mil jobs não tentem em exatamente o mesmo segundo.
Capture o básico sempre que um job falhar para que você possa reproduzir depois:
- Nome do job, ID único do job e número da tentativa
- Payload exato (ou um resumo seguro) e IDs relevantes
- Mensagem de erro e stack trace, além do código de resposta upstream se foi uma chamada API
- Informações de tempo: quanto o job rodou e quanto até o próximo retry
Alertas podem ser simples. Se a profundidade da fila crescer por 10 minutos, ou a idade do job mais antigo passar um limite, notifique alguém ou ao menos envie uma mensagem a um canal compartilhado. Isso pega falhas “silenciosas” onde o worker está vivo mas todo job está preso.
Exemplo: um job semanal de e-mail atinge o limite de taxa do provedor na manhã de segunda. Sem backoff, ele retrya imediatamente, consome todas as tentativas e descarta e-mails. Com backoff e um log claro de “rate limited”, ele espera, se recupera, e você pode confirmar a correção.
Idempotência: tornar jobs seguros para rodar duas vezes
Quando jobs voltam a rodar (ou retries começam a funcionar), frequentemente aparece um novo problema: o mesmo job roda duas vezes. Se o job não for idempotente, “duas vezes” pode significar cobrar um cliente em dobro, enviar e-mails duplicados ou criar registros duplicados.
Idempotência significa que você pode executar o mesmo job mais de uma vez e ainda obter um único resultado correto. Imagine um job “cobrar cartão e enviar recibo”. Um problema de rede pode acontecer depois que o pagamento foi bem-sucedido mas antes do app salvar o resultado. A fila retrya e o cliente é cobrado novamente.
Onde chaves de idempotência ajudam mais
Para qualquer coisa que fale com o mundo externo (pagamentos, provedores de e-mail, SMS, webhooks), adicione uma chave de idempotência que permaneça a mesma para a ação lógica, não para a tentativa. Uma boa chave está ligada a um ID de negócio estável, tipo invoice_1234 ou order_987.
Proteja também seu banco contra duplicatas. A defesa mais simples é uma constraint única, como “apenas um recibo por invoice”. Então seu job pode tentar inserir e tratar “já existe” como sucesso.
Padrões práticos que funcionam bem ao mover um protótipo para produção:
- Grave um estado do job primeiro (por exemplo:
payment_pending), depois faça a chamada externa, e então marque comopayment_completed. - Armazene uma “processed key” (ID do job ou ID de negócio) e verifique antes de fazer efeitos colaterais.
- Use constraints únicas para coisas one-time (recibos, e-mails, assinaturas).
- Retorne sucesso se o resultado já existir.
Se você herdou um protótipo gerado por IA, isso frequentemente falta: jobs fazem o efeito colateral primeiro e só depois tentam salvar. Corrigir essa ordem já elimina a maior parte das cobranças e e-mails duplicados.
Passo a passo: um fluxo repetível de depuração
Pare de chutar e teste um ponto da pipeline por vez. O objetivo é transformar um sintoma vago (e-mails não enviados, relatórios não gerados) num ponto de falha claro que você possa corrigir.
Use um job pequeno de teste com um payload conhecido e mantenha todo o resto igual ao ambiente de produção:
- Prove que o scheduler dispara: rode o scheduler manualmente uma vez (ou temporariamente ajuste o cron para a cada minuto) e confirme que ele tenta enfileirar o job.
- Prove que o job é criado: verifique a fila, a tabela de jobs ou o broker por uma nova entrada com seu payload conhecido e timestamp.
- Prove que um worker consegue ver: inicie um único worker em primeiro plano e observe-o pegar o job.
- Leia o erro real: encontre a primeira falha (variável de ambiente faltando, URL do banco errada, e-mail bloqueado, permissões).
- Reexecute o mesmo job: confirme que ele completa, e então confirme que não aplica mudanças duas vezes se rodado novamente.
Depois disso você saberá qual camada está quebrada: agendamento, enfileiramento, deploy/conectividade do worker ou lógica do job.
Depois que funcionar uma vez, adicione guardrails
Um job que roda só quando você está olhando ainda não está consertado. Adicione proteções para que falhas não se acumulem silenciosamente:
- Timeouts e limites (para que jobs não fiquem pendurados para sempre)
- Regras claras de retry com backoff (para que falhas não entrem em loop)
- Dead-letter ou fila de falha (para que jobs ruins não bloqueiem os bons)
- Checagens de idempotência (para que retry não duplique envios ou cobranças)
- Logs que incluam ID do job e campos-chave do payload
Armadilhas comuns que impedem jobs de completar
Frequentemente a causa não é o sistema de filas em si. É uma pequena incompatibilidade entre onde o scheduler roda, onde o worker roda e o que o job precisa para ter sucesso.
Uma falha comum é dividir a pipeline por acidente. Por exemplo, cron roda no servidor web, mas o worker é implantado em outra máquina ou container que não está ativo em produção. O scheduler enfileira felizmente, mas ninguém está lá para pegar os jobs.
Outras armadilhas repetidas:
- Usar uma fila em memória (ou adapter só para dev) de modo que jobs somem no restart ou ao escalar
- Capturar erros e retornar “sucesso” mesmo assim, fazendo o job parecer terminado mas sem efeito
- Falta de visibilidade de falhas, de modo que jobs quebrados se acumulam por dias
- Segredos e endpoints hardcoded que diferem entre local, staging e prod
- Jobs dependendo de arquivos locais ou pastas temporárias que não existem no worker
Um pequeno exemplo: um job envia faturas e chama uma API externa. Localmente funciona porque a API key está no seu .env. Em produção, o container do worker não tem essa variável, o código captura a exceção e o job completa sem enviar a fatura. Você só percebe quando clientes reclamam.
Se suspeitar de uma dessas, responda duas perguntas: onde o job roda (qual serviço) e onde você vê seus logs? Se não conseguir encontrar ambos, as falhas podem ficar escondidas por muito tempo.
Checklist rápido antes de enviar novamente
Antes de subir outra versão, faça uma verificação rápida por toda a pipeline. A maioria dos relatos “tarefas em segundo plano não estão rodando” não é um único bug. São pequenas diferenças entre agendamento, enfileiramento e configuração do worker.
Comece pelo scheduler. Em muitos protótipos, o processo de cron nunca roda em produção, ou roda num container que é reduzido. Confirme que ele está rodando agora (não apenas “configurado”), e que tem as mesmas variáveis de ambiente do app.
Depois, confirme que jobs estão realmente sendo criados. Verifique se o timestamp do job (run at / scheduled for) bate com o fuso de produção, e se o payload tem IDs reais (não strings vazias, e-mails placeholder ou user IDs nulos). Se jobs são criados mas agendados para muito no futuro, vão parecer “presos” apesar de nada estar quebrado.
Então verifique o worker. Garanta pelo menos um processo worker rodando continuamente e escutando o exato nome de fila que seu app usa. Um único erro de digitação ou mismatch de fila pode criar acúmulos silenciosos.
Finalmente, torne falhas visíveis e seguras para retry:
- Logue erros com contexto suficiente (nome do job, ID, campos chave)
- Limite retries e use backoff, para que falhas não entrem em loop
- Faça jobs idempotentes, para que retry não duplique envios, cobranças ou registros
Exemplo: o job de e-mail semanal que funciona localmente mas não em produção
Um caso comum “funcionou no meu laptop”: um protótipo envia um relatório semanal toda segunda às 9:00. Após o deploy, nada chega. O dashboard parece ok, usuários conseguem fazer login e não há erros óbvios.
Primeiro check: o agendamento existe em produção? Localmente seu servidor dev pode rodar um scheduler automaticamente (ou você iniciou uma vez e esqueceu). Em produção, a entrada de cron existe no config, mas não há um processo scheduler de fato rodando. O código está certo, mas nada dispara a fila.
Depois de implantar um scheduler (ou habilitar o cron da plataforma), jobs começam a aparecer. Isso revela o segundo problema: o worker fica retryando o mesmo job porque falta uma variável de ambiente para o provedor de e-mail. Cada execução falha, refila e acumula backlog. Novos jobs atrasam e a fila parece “presa”, mesmo que o worker esteja ocupado falhando.
O que conserta de vez:
- Adicionar um log simples de saúde quando o scheduler enfileira jobs (para provar que triggers estão acontecendo)
- Falhar rápido se variáveis essenciais faltam na inicialização (para que o worker caia alto em vez de retryar eternamente)
- Limitar retries com backoff (por exemplo, 5 tentativas em 30 minutos), depois mover o job para dead-letter para revisão
- Tornar o job de e-mail idempotente salvando uma chave única como
report:teamId:weekStartDateantes de enviar, e pular se já existir
Com essas mudanças você pode reiniciar workers, redeployar ou recuperar de crashes sem enviar duplicados ou criar loops infinitos de retry.
Próximos passos: endurecer a pipeline e mantê-la estável
Uma vez que os jobs estejam rodando de novo, o objetivo é evitar que a mesma quebra volte na próxima semana. Trate tarefas em segundo plano como um risco de produção, não como um bug pontual.
Transforme o que aprendeu em um runbook curto que qualquer pessoa possa seguir:
- Onde os agendamentos são definidos (config de cron, scheduler da plataforma, timers do app)
- Como os workers são iniciados (nome do processo, comando, variáveis de ambiente necessárias)
- O que significa “saudável” (profundidade da fila, último horário de execução bem-sucedida)
- Os principais modos de falha que você viu (timeouts, auth, payloads ruins)
- A forma segura de reproduzir jobs (e como evitar envios duplicados)
Adicione monitoramento mínimo que responda duas perguntas: os jobs estão se acumulando, e eles estão falhando? Mesmo alertas básicos sobre crescimento da fila e contagem diária de jobs falhados pegam a maioria dos problemas cedo.
Se seu protótipo mistura código de requisição web e lógica de job, planeje um pequeno refactor. Mova o núcleo do trabalho para uma função única que rode no worker, e faça a requisição web apenas enfileirar e validar input. Isso deixa retries mais seguros e remove dependências escondidas do estado da requisição.
Se você herdou um app gerado por IA e precisa de um segundo par de olhos rápido, FixMyMess (fixmymess.ai) foca em diagnosticar e reparar protótipos quebrados para que filas, workers, retries e comportamento de deploy batam com o que você via localmente — com verificação humana antes do envio.
Perguntas Frequentes
Como sei se é um problema de tarefas em segundo plano e não um bug normal do app?
Se as páginas carregam, mas ações “depois” nunca acontecem (e-mails não chegam, importações ficam em processamento, webhooks recebidos mas sem atualizações), assuma que as tarefas em segundo plano estão falhando. Confirme verificando se um job chegou a ser enfileirado e se algum worker tentou processá-lo próximo ao horário da ação do usuário.
Por que tarefas em segundo plano funcionam localmente mas não em produção?
No desenvolvimento local é comum tudo rodar num só lugar: servidor web, scheduler e worker. Em produção, essas partes ficam em processos ou serviços separados, então uma peça faltando (scheduler não rodando, worker não implantado, conexão com a fila errada) quebra a cadeia mesmo com o app web parecendo ok.
Qual é a forma mais rápida de identificar onde o pipeline está quebrando?
Comece decidindo qual estágio está quebrado: criação do job, pickup pelo worker ou execução do job. Procure um log de enqueue ou um registro do job; se não existir, geralmente é agendamento. Se jobs existem mas ficam enfileirados, é problema do worker ou conectividade. Se as tentativas aumentam, o job está rodando e falhando — foque no erro e nas regras de retry.
Quais são os erros mais comuns de agendamento (cron)?
Agendamento falha quando nenhum job é criado na hora esperada. Causas comuns: scheduler não rodando em produção, diferenças de fuso horário (local vs UTC), ambiente errado configurado para o cron, ou formato de cron incompatível (5 campos vs 6 campos). Uma checagem rápida é logar os próximos horários de execução e confirmar se batem com sua expectativa.
Qual a diferença entre fila e worker, e o que costuma quebrar mais?
A fila é o lugar onde jobs aguardam, e o worker é o processo que pega e executa esses jobs. Se os jobs se acumulam como “queued”, normalmente o worker não está rodando, está apontando para outro nome de fila, ou não consegue alcançar o backend da fila por causa de credenciais ou rede.
Por que meu worker parece “saudável” mas ainda assim não processa jobs?
Frequentemente o app web e o worker têm conjuntos diferentes de variáveis de ambiente. O app pode enfileirar corretamente enquanto o worker está sem a URL da fila, sem a URL do banco de dados ou sem uma chave de API, então inicia mas não processa jobs. Verifique se o worker tem as mesmas variáveis críticas do processo web e se loga algo claro como “conectado à fila” na inicialização.
Se os retries continuam, isso significa que o agendamento está ok?
Não necessariamente; pode indicar que o job está falhando imediatamente e sendo reenfileirado. Verifique se o número de tentativas está aumentando e se ocorre o mesmo erro repetidamente. Se for o caso, adicione um limite de tentativas e backoff para que falhas não fiquem em loop, e faça com que erros permanentes vão para um estado de falha revisável em vez de retry infinito.
Como evito e-mails duplicados ou cobranças em dobro quando jobs são reexecutados?
Idempotência significa que o job pode rodar duas vezes e ainda produzir um único resultado correto. Use chaves de idempotência estáveis para efeitos externos (pagamentos, e-mails, SMS) e proteja o banco com constraints únicas para que “já existe” seja tratado como sucesso. Assim você evita cobranças duplicadas e e-mails repetidos quando há retries após sucesso parcial.
Que informações devo coletar antes de alterar o código?
Registre o timestamp da ação do usuário (ou do horário programado), o nome/tipo do job e qualquer ID de job, logs do worker naquele período, profundidade da fila e contagem de falhas, e o último horário de execução bem-sucedida do mesmo job. Esse “pacote de evidências” mostra rapidamente se jobs não são criados, não são consumidos ou estão falhando em execução.
Quando devo chamar a FixMyMess em vez de depurar por mais tempo?
Se você herdou um protótipo gerado por IA e jobs estão presos, falhando silenciosamente ou duplicando efeitos, provavelmente é uma incompatibilidade de deploy e pipeline em vez de um único bug. FixMyMess pode fazer uma auditoria gratuita do código para apontar se falta um scheduler, se um worker está morto, ou se há problemas com segredos e retries, e então reparar e endurecer a configuração com verificação humana.