Por que o cron job não é executado?
Veja cinco causas comuns: PATH mínimo, % sem escape, crontab errado, saída perdida por email e script que depende da sua shell de login.
Por que o seu cron job não é executado
Um cron job que “nunca é executado” quase sempre foi executado. Ele foi executado num ambiente que não é a sua shell, falhou no primeiro segundo e a mensagem foi enviada para um local que não está a consultar. Cinco causas explicam quase todos os casos: o caminho de pesquisa, o sinal de percentagem, o ficheiro crontab errado, a saída enviada por email e um script que espera uma sessão de login.
O cron é um daemon (um serviço em segundo plano) que lê ficheiros crontab e inicia comandos segundo um agendamento. Não lê o seu .bashrc, não abre um terminal, não inicia uma shell de login e não informa quando um comando falha. Todas as causas abaixo resultam destes quatro factos.
Analise-as pela ordem apresentada e comece pela questão subjacente a todas elas: o cron chegou a disparar? “O cron nunca iniciou o job” e “o job foi iniciado e terminou” são problemas diferentes, sem relação entre si. Responda primeiro a essa questão.
O cron chegou a ser executado?
O daemon tem um nome de unidade diferente em cada família de distribuições. Verifique ambos e leia o log.
systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"Debian e Ubuntu chamam a unidade de cron. Fedora, Rocky e Alma chamam-na de crond. Apenas um desses nomes existe numa determinada máquina. Por isso, é normal que um dos dois comandos indique que a unidade não é conhecida; isso não é uma falha.
Leia as entradas gravadas pelo seu próprio sistema. Não procure uma linha copiada de um guia, porque o texto varia entre implementações do cron e configurações de logging. Verifique apenas duas coisas: se existe uma entrada no minuto indicado pela sua agenda e se essa entrada identifica o seu comando. Uma entrada que identifica o seu comando significa que o cron fez a parte dele e que a falha está dentro do comando. A ausência total de entradas significa que o cron nunca recebeu a sua agenda. Essa é a causa 3 abaixo.
Algumas imagens enviam as mensagens do cron através do rsyslog para um ficheiro, em vez de as enviarem para o journal. Procure em /var/log um ficheiro com um nome relacionado com cron ou syslog e leia o fim desse ficheiro.
ls -l /var/log
sudo tail -n 50 /var/log/syslogSe não existir nem a unidade nem o log, é possível que o cron simplesmente não esteja instalado. Imagens cloud mínimas e contentores frequentemente não o incluem.
dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cronCausa 1: o cron não tem o seu PATH
O seu shell interativo constrói PATH a partir de /etc/profile, ~/.profile, ~/.bashrc e de tudo o que esses ficheiros carregam. Nada disso é executado por um job do cron. O cron inicia o comando com o seu próprio ambiente reduzido. Por isso, um programa localizado fora dos diretórios de sistema padrão não é encontrado. Qualquer programa em /usr/local/bin, /opt, num gestor de versões de linguagem, num ambiente virtual Python ou num workspace Go pode estar nessa situação. O job falha na primeira linha e o shell escreve um erro semelhante a "not found". A mensagem exata depende do shell que o executou.
Encontre o caminho real de todos os comandos usados pelo job.
command -v docker
command -v node
readlink -f "$(command -v node)"Depois, escreva esses caminhos absolutos no job ou defina PATH uma vez no início do crontab.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool runObtenha essa lista na sua própria máquina com echo "$PATH" e remova tudo o que exista apenas numa sessão interativa. Há uma regra importante: o cron não expande variáveis nestas linhas de atribuição. PATH=$PATH:/usr/local/bin guarda o texto literal $PATH:/usr/local/bin, pelo que o job fica com um caminho de pesquisa sem nenhum diretório utilizável. Escreva a lista completa.
Um gestor de versões precisa de mais do que um caminho. nvm, pyenv, rbenv e asdf instalam uma função de shell ou um diretório de shims a partir de .bashrc, mas um job do cron nunca lê esse ficheiro. Chame o binário versionado pelo caminho absoluto ou carregue o script de inicialização do gestor como a primeira linha do seu próprio script.
Causa 2: o sinal de percentagem encerra o seu comando
No campo de comando de um crontab, % não é um caractere comum. O primeiro % não escapado encerra o comando. Tudo o que vem depois é enviado ao comando como entrada padrão, e cada % adicional transforma-se numa nova linha. Esta é uma funcionalidade real do cron para fornecer entradas curtas a um programa. É também por isso que um nome de ficheiro com data é o exemplo clássico de uma entrada de crontab que não funciona.
Escreva 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site e o tar nunca recebe uma data formatada. O cron corta a linha no primeiro %. Assim, o shell recebe uma substituição de comando incompleta, e o restante da linha chega como entrada padrão. Escape cada sinal de percentagem com uma barra invertida.
0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/siteDuas camadas leem essa linha, por esta ordem. \% é uma regra do cron, aplicada pelo cron antes de iniciar qualquer processo. $(date +\%F) é uma substituição de comando, aplicada posteriormente pelo shell iniciado pelo cron. O ponto essencial é saber qual camada interpreta cada caractere.
A prática mais segura é manter a lógica fora do crontab. Coloque-a num script, onde o sinal de percentagem não tem significado especial.
#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/siteA linha do crontab passa então a conter apenas um caminho e um redirecionamento. Um crontab que pode ser lido de relance também pode ser depurado com facilidade.
Causa 3: qual crontab editou?
Não existe um único crontab. Existem vários ficheiros, com proprietários diferentes e números de campos diferentes. Um job escrito no ficheiro errado fica invisível.
crontab -eedita o crontab do utilizador que executa o comando.sudo crontab -eedita o crontab do root. Duas pessoas a depurar o mesmo servidor acabam muitas vezes por ler dois ficheiros diferentes.sudo crontab -l -u deploylista o crontab de outro utilizador. É assim que confirma o que está realmente instalado para a conta que deve executar o job./etc/crontabe todos os ficheiros em/etc/cron.dtêm um campo adicional entre o agendamento e o comando: o utilizador com que o job será executado. Se colar uma linha de crontab de utilizador, com cinco campos, em/etc/cron.d, a primeira palavra do comando será interpretada como um nome de utilizador.- Os ficheiros em
/etc/cron.dtêm de usar apenas letras, dígitos, underscores e hífenes no nome. Um ficheiro chamadobackup.shousite.confé ignorado apenas por causa do nome. Mude o nome parabackupe volte a verificar o log. - Os ficheiros em
/etc/cron.ddevem pertencer ao root e não podem ter permissões de escrita para o grupo ou para outros utilizadores.ls -l /etc/cron.dmostra os dois factos ao mesmo tempo. - Os scripts colocados em
/etc/cron.dailye nos diretórios semelhantes seguem a mesma regra de nomes e também têm de ter o bit de execução definido. A ausência do bit de execução faz com que sejam ignorados silenciosamente. /etc/cron.allowe/etc/cron.denydeterminam quem pode instalar um crontab. Se algum deles existir no servidor, leia-o antes de assumir que o seu utilizador pode ter um.
Instale um crontab de utilizador com o comando crontab, em vez de editar manualmente o ficheiro de spool, porque crontab analisa o ficheiro antes de o instalar. Ao guardar, leia o que o comando devolve. Se rejeitar o ficheiro, a versão anterior continua ativa e a alteração nunca entra em vigor. Isto parece exatamente que o cron está a ignorar os seus comandos.
O proprietário também determina as permissões. Um job no crontab do root cria ficheiros pertencentes ao root que a aplicação que os lê pode não conseguir escrever. Um job no crontab de um utilizador normal não consegue ler um diretório acessível apenas ao root. Associe o proprietário ao trabalho: a manutenção da aplicação deve pertencer à própria conta da aplicação. Esse é o motivo de substituir o wp-cron do WordPress por um job do cron do sistema. O modo dos ficheiros criados pelo job vem do umask que ele herda. Esse valor também pode ser diferente do umask da sua shell. Por isso, consulte como o umask define as permissões dos ficheiros se a saída de um job ficar sem permissões de leitura.
Causa 4: a saída foi enviada para uma caixa de correio que ninguém lê
O cron recolhe tudo o que um job escreve na saída padrão e no erro padrão. Se o job escrever qualquer coisa, o cron entrega esse texto ao sistema de correio local, endereçado ao proprietário do crontab ou ao que MAILTO indicar. Num VPS simplificado, normalmente não existe nenhum MTA (mail transfer agent) instalado, por isso nada entrega essa mensagem. O erro existiu durante alguns instantes e depois não chegou a lado nenhum. Essa é a razão pela qual um job com problemas parece silencioso.
Envie a saída para um ficheiro que possa controlar.
0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1>> acrescenta a saída padrão ao ficheiro. 2>&1 encaminha o erro padrão para o destino atual da saída padrão, por isso tem de aparecer depois do redirecionamento. Se escrever na ordem inversa, como 2>&1 >> file, o erro padrão mantém o destino original, e o erro que está a procurar é precisamente a parte que nunca chega ao ficheiro.
O journal é o outro destino adequado. logger escreve no syslog com uma tag à sua escolha.
0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-siteLeia a saída com journalctl -t backup-site. Assim, a saída do próprio job fica junto das entradas do cron, o que facilita acompanhar a sequência temporal. Se também precisar de registar qual utilizador executou cada comando no sistema, isso é um sistema separado. Consulte auditar os comandos dos utilizadores no seu servidor.
MAILTO="" no início de um crontab desativa o envio de correio para os jobs abaixo dessa linha. Definir MAILTO como um endereço real só ajuda se existir um MTA funcional. Por isso, confirme primeiro que o correio sai do sistema antes de depender desse mecanismo.
Uma regra durante a depuração: nunca acrescente > /dev/null 2>&1. É a linha mais comum em todos os crontabs e elimina a única evidência disponível. Volte a colocá-la mais tarde, se quiser, depois de o job funcionar.
Causa 5: o script pressupõe um ambiente que o cron não fornece
Depois de localizar o comando e capturar a sua saída, resta tudo o que a sua sessão fornece automaticamente.
- A shell pode não ser bash. Verifique com
ls -l /bin/sh. No Debian e no Ubuntu, aponta para dash, por isso o teste com colchetes duplos, os arrays esourcefalham com um erro de sintaxe. Inclua uma linha#!/bin/bashno script e execute o script, ou definaSHELLno início do crontab. - O diretório de trabalho não é aquele em que estava. Use caminhos absolutos em todo o lado ou
cdpara mudar para o diretório na primeira linha do script. Um caminho relativo é a razão isolada mais comum para uma tarefa "funcionar quando a executo manualmente". - O locale não é o da sua sessão. Qualquer comando que formate uma data ou um número, ou ordene texto, pode produzir uma saída diferente com outro
LANG. Se uma etapa posterior analisar essa saída, defina o locale no script em vez de depender desse comportamento. - Não existe um TTY (terminal). Um comando que peça confirmação, abra um editor ou apresente uma barra de progresso pode ficar bloqueado ou terminar. Adicione a flag não interativa disponibilizada pela ferramenta.
- Não existe um agente SSH.
SSH_AUTH_SOCKnão está no ambiente do cron, por isso um comandosshoursyncque funcionava porque o seu agente tinha uma chave carregada deixa de conseguir autenticar. Forneça à tarefa a sua própria chave, pertencente ao utilizador que executa a tarefa. - Não existe um user session bus, por isso
systemctl --usera partir de uma tarefa do cron falha atéXDG_RUNTIME_DIRser definido. Uma system unit é a solução mais adequada.
No Fedora, Rocky e Alma existe mais uma possibilidade. O SELinux restringe as tarefas do cron, por isso uma tarefa que aceda a um caminho com um label inesperado é bloqueada mesmo quando as permissões do ficheiro parecem corretas. Procure bloqueios com sudo ausearch -m avc -ts recent e leia noções básicas de SELinux para um servidor antes de desativar qualquer componente.
A sonda de um minuto que mostra o ambiente do cron
Pare de tentar adivinhar o que existe no ambiente do cron e leia o conteúdo. Escreva um script que descarregue tudo, agende-o para ser executado a cada minuto, aguarde e depois leia o ficheiro.
cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.shAdicione uma linha à crontab do utilizador que executa o trabalho real, usando caminhos absolutos nos dois lados.
* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1Aguarde um minuto e leia /home/deploy/cron-probe.log. Compare-o com os mesmos comandos executados na sua própria shell. A linha PATH, o diretório de trabalho e a localidade normalmente explicam a falha por si só. Tenha em atenção dois detalhes da configuração: os sinais de percentagem estão dentro do script, onde a regra do cron não se aplica, e o caminho do log é um local onde o utilizador do trabalho pode escrever.
Apague essa linha da crontab assim que obtiver a resposta. Um trabalho executado a cada minuto e que acrescenta dados a um ficheiro enche um disco pequeno, e fá-lo silenciosamente.
O agendamento é o que pretendia?
Uma linha do crontab de um utilizador começa com cinco campos: minuto, hora, dia do mês, mês e dia da semana. Dois deles interagem de uma forma que causa surpresas.
Quando o dia do mês e o dia da semana estão ambos restringidos, ou seja, nenhum deles é *, o cron executa o job quando qualquer um dos campos corresponde. 0 0 13 * 5 não significa "sexta-feira 13". Executa o job à meia-noite do dia 13 de cada mês e à meia-noite de todas as sextas-feiras. Para obter um único dia específico, deixe um dos dois campos como * e teste o outro dentro do script.
O cron usa o fuso horário do sistema. Muitas imagens de VPS vêm configuradas para UTC (tempo universal coordenado). Por isso, um job agendado para as 03:00 é executado às 03:00 UTC, que pode corresponder ao meio da tarde no seu fuso horário. timedatectl mostra o fuso horário efetivamente usado pelo seu sistema. Consulte esse valor em vez de assumir que corresponde ao do seu portátil.
Também é importante conhecer mais duas armadilhas dos agendamentos. @reboot é acionado quando o próprio cron inicia. Isso não corresponde necessariamente ao momento em que a rede fica disponível. Por isso, um job que precise de DNS ou de um host remoto pode falhar no arranque e funcionar em todas as execuções manuais seguintes. Além disso, nada impede que um job lento volte a iniciar enquanto a execução anterior ainda está em curso. Envolva-o num lock.
*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1flock -n termina imediatamente quando o lock já está ocupado. Assim, a execução sobreposta para em vez de se acumular sobre a primeira.
Quando o timer do systemd é a ferramenta mais adequada
O cron é bom para uma coisa: executar este comando neste horário. É limitado em quase todos os outros aspetos. Um timer fornece o journal sem redirecionamento, um status de saída que pode consultar mais tarde, ordenação em relação a network-online.target e um atraso aleatório para que uma centena de servidores não inicie tudo no mesmo segundo. Quando o seu job precisa de qualquer uma destas funcionalidades, um serviço e timer do systemd numa VPS dá menos trabalho do que manter uma linha no crontab. O comportamento de novas tentativas também deve ser definido aí, porque as políticas de reinício do systemd determinam o que acontece depois de uma falha, enquanto o cron não tem resposta para essa questão.
Mantenha o cron para jobs pequenos. Transfira para um timer tudo o que tenha dependências ou uma política de novas tentativas. Ambos podem ser executados no mesmo servidor, portanto não precisa de concluir esta migração numa única sessão.
FAQ
Por que a minha tarefa do cron funciona manualmente, mas falha quando é executada pelo cron?
Porque o seu shell e o ambiente do cron são diferentes. O seu shell de login lê /etc/profile e ~/.bashrc, que definem PATH, a localidade e as variáveis do seu agent. O cron inicia o comando sem nada disso, a partir de um diretório de trabalho diferente e, por vezes, com outro shell. Use caminhos absolutos para todos os comandos, defina o que for necessário no início do crontab ou dentro do script e agende uma tarefa de teste para executar durante um minuto env | sort, pwd e id num ficheiro de log. Assim, pode consultar o ambiente real do cron em vez de o deduzir.
Como verifico se o cron executou efetivamente a minha tarefa?
Leia o log do daemon. Use journalctl -u cron em Debian e Ubuntu, ou journalctl -u crond em Fedora, Rocky e Alma; algumas imagens encaminham as mensagens através do rsyslog para um ficheiro em /var/log. Procure uma entrada no minuto indicado pela sua agenda e confirme que ela identifica o seu comando. A ausência de uma entrada significa que o cron nunca recebeu essa agenda. Confirme se editou o crontab correto. Uma entrada sem resultado significa que o comando foi iniciado e terminou. Nesse caso, capture a saída com um redirecionamento.
Por que date +%Y falha dentro de um crontab?
O cron trata % como um caractere especial no campo do comando. O primeiro % não escapado termina o comando. Tudo o que vem depois é passado a esse comando como entrada padrão, e cada % adicional torna-se uma nova linha. Por isso, um nome de ficheiro formatado com uma data nunca chega ao programa para o qual foi escrito. Escape cada sinal de percentagem como \%, ou mova o comando para um script e invoque o script a partir do cron. Dentro de um script, o sinal de percentagem não tem significado especial.
Para onde vai a saída da minha tarefa do cron?
Vai para o sistema de correio local, endereçada ao proprietário do crontab ou ao nome definido por MAILTO. A maioria das imagens de VPS não tem um agente de transferência de correio instalado. Por isso, a mensagem é descartada e a tarefa parece não produzir saída. Redirecione a saída para um ficheiro com >> /path/to/log 2>&1, mantendo essa ordem para que o erro padrão siga a saída padrão, ou encaminhe-a através de logger -t myjob e leia-a novamente com journalctl -t myjob. Não use > /dev/null 2>&1 enquanto ainda estiver a depurar.
Devo usar cron ou um temporizador do systemd?
Use o cron para um comando simples executado a uma hora fixa, especialmente se precisar de mover a tarefa para uma máquina que não execute systemd. Use um temporizador quando quiser a saída no journal sem redirecionamentos, um estado de saída consultável, ordenação depois de a rede estar ativa, um atraso de início aleatório ou uma política de novas tentativas após uma falha. Ambos podem ser executados no mesmo servidor. Assim, pode migrar as tarefas uma a uma quando fizer sentido.