Tutoriais de VPS
Tarefas agendadas da VPS não executam (2026): checklist de cron, crontab, systemd timer e variáveis de ambiente
Quando tarefas do crontab ou do systemd timer não executam em uma VPS, a causa comum não é o comando em si, mas o usuário de execução, o ambiente PATH, o diretório de trabalho, as permissões, o fuso horário, o serviço não habilitado ou os logs não lidos corretamente. Este artigo investiga camada por camada: serviço cron, sintaxe das tarefas, logs, variáveis de ambiente, systemd timer e idempotência das tarefas.

Tarefas agendadas que não executam em uma VPS é um dos erros de diagnóstico mais fáceis de cometer.
O mesmo comando que você executa manualmente via SSH funciona sem problema, mas quando você o coloca em crontab simplesmente não roda; o script funciona sozinho normalmente, mas o backup automático de madrugada não gera nenhum arquivo; systemctl status também não parece mostrar erro em vermelho, mas o lado do negócio nunca recebe o resultado. Na maioria dos casos, isso não é "misticismo de tarefas agendadas do Linux", mas sim o ambiente de execução sendo completamente diferente do seu login manual.
Este artigo segue uma ordem de investigação: primeiro confirmar se o cron ou o systemd timer realmente está rodando, depois olhar logs, usuários, caminhos, permissões, variáveis de ambiente e fuso horário, e por fim tratar da idempotência e dos alertas do próprio script.
Não comece editando o crontab. Primeiro divida o problema em duas categorias:
- Nunca disparou: não há registro de execução da tarefa nos logs, geralmente é um problema do serviço cron, da sintaxe, do usuário ou do timer não habilitado.
- Disparou mas falhou: há registro nos logs, mas o script dá erro, as permissões são insuficientes, o comando não é encontrado ou o arquivo de saída não é gerado.
- Executou mas o resultado não é o esperado: por exemplo, o arquivo de backup está vazio, a sincronização com o armazenamento de objetos falha ou o script de reinício roda repetidamente.
Olhar primeiro os logs do sistema economiza muito tempo. Se você ainda não está familiarizado com journalctl e logs de serviços, pode ler primeiro este artigo: Como ler os logs da VPS (journalctl, systemctl, Nginx, logs de aplicação).
No Debian / Ubuntu normalmente é cron, e no RHEL / CentOS / Rocky normalmente é crond.
systemctl status cron
systemctl status crond
Se o serviço não estiver iniciado:
sudo systemctl enable --now cron
Ou:
sudo systemctl enable --now crond
Depois veja os logs recentes:
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"
Se o sistema não tiver o serviço correspondente, primeiro confirme se o pacote cron está instalado:
dpkg -l | grep cron
rpm -qa | grep cron
Algumas imagens mínimas de VPS não vêm com cron pré-instalado, especialmente imagens em contêiner ou imagens cloud muito limpas. Não presuma por padrão que ele sempre existe.
Esta é uma das armadilhas mais comuns.
Você executa como usuário comum:
crontab -e
Isso edita as tarefas agendadas do usuário atual. Você executa como root:
sudo crontab -e
Isso edita as tarefas agendadas do root. Não são o mesmo arquivo.
Verifique as tarefas do usuário atual:
crontab -l
Verifique as tarefas do root:
sudo crontab -l
Se o script precisa acessar /root/.ssh, ler um diretório de backup que só o root pode ler ou reiniciar serviços, normalmente ele deve ir no crontab do root ou ser alterado para um systemd timer. Por outro lado, se o script depende do diretório de projeto de um usuário comum, de um virtualenv do Python ou de um gerenciador de versões do Node, não o coloque sem cuidado no crontab do root.
Primeiro adicione uma tarefa que apenas escreve um log, não chame diretamente um script complexo:
* * * * * date >> /tmp/cron-test.log 2>&1
Aguarde 1-2 minutos e verifique:
cat /tmp/cron-test.log
Se isso também não gerar saída, o problema está no serviço cron, no arquivo crontab ou na hora do sistema.
Se isso gerar saída mas o seu script não rodar, o problema provavelmente está no caminho do script, nas permissões, nas variáveis de ambiente ou no usuário de execução.
Lembre-se de remover esta tarefa após o teste para evitar escrever em /tmp por muito tempo.
O crontab de usuário tem 5 campos de tempo mais o comando:
分 时 日 月 周 命令
Por exemplo, para executar às 3:10 da madrugada todos os dias:
10 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Mas os arquivos em /etc/crontab e /etc/cron.d/ têm um campo extra de "usuário":
10 3 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Muitas pessoas copiam o formato do crontab de usuário para /etc/cron.d/, ou copiam ao contrário, o que faz as tarefas não executarem como esperado.
Também é preciso observar alguns pontos:
- É melhor manter uma quebra de linha no final do arquivo crontab.
- Os nomes de arquivo em
/etc/cron.d/não devem ter sufixos estranhos; as permissões normalmente são644. - O
%nos comandos tem significado especial no cron; formatos de data comodate +%Fprecisam ser escritos comodate +\%F.
Você consegue executar isto via SSH:
node app.js
python script.py
rclone sync ...
Isso não significa que o cron também consiga encontrar esses comandos. O ambiente do cron é muito limpo, e PATH geralmente é apenas um trecho bem curto.
Primeiro descubra o caminho completo do comando:
which node
which python3
which rclone
which mysqldump
Escreva o caminho absoluto no crontab:
10 3 * * * /usr/bin/python3 /opt/jobs/cleanup.py >> /var/log/cleanup.log 2>&1
Ou defina explicitamente o PATH no topo do crontab:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Se você usa ambientes de nível de usuário como nvm, pyenv, conda, o cron não os carrega por padrão. Uma abordagem mais confiável é escrever um script wrapper que carregue o ambiente dentro do script e depois execute o comando real.
Ao executar manualmente, você pode estar posicionado no diretório do projeto:
cd /var/www/app
npm run task
Quando o cron executa, o diretório de trabalho não é necessariamente este. Se o script usa caminhos relativos, como ./backup, .env, logs/app.log, é muito fácil falhar.
Recomenda-se escrever a tarefa assim:
15 2 * * * cd /var/www/app && /usr/bin/npm run task >> /var/log/app-task.log 2>&1
Se o script for importante, é melhor fixar o diretório no início do script:
#!/usr/bin/env bash
set -euo pipefail
cd /var/www/app
Depois faça o crontab chamar este script:
15 2 * * * /usr/local/bin/app-task.sh >> /var/log/app-task.log 2>&1
Quando o script não tem permissão de execução, o log do cron pode deixar apenas uma linha como Permission denied.
Verifique:
ls -l /usr/local/bin/app-task.sh
Corrija:
sudo chmod +x /usr/local/bin/app-task.sh
Se a tarefa precisa gravar em um diretório de backup, diretório de logs ou enviar arquivos temporários, também confirme que o usuário de execução tem permissão:
sudo -u www-data /usr/local/bin/app-task.sh
sudo -u backup /usr/local/bin/backup.sh
Executar manualmente com o usuário de destino é mais útil como referência do que executar uma vez como root.
Uma tarefa agendada sem logs dobra o custo de investigação.
No mínimo, escreva assim:
30 1 * * * /usr/local/bin/backup.sh >> /var/log/backup-cron.log 2>&1
Se o log for crescer com o tempo, combine com logrotate, ou faça o script gravar logs por data. Não deixe as tarefas agendadas falharem silenciosamente.
Você também pode adicionar no início do script:
set -euo pipefail
Assim, variáveis indefinidas, pipes com falha e comandos com falha são expostos mais cedo. Só que antes de adicionar esta linha a scripts em produção, teste, para evitar transformar etapas que originalmente toleravam falhas em falhas duras.
O fuso horário padrão das imagens de VPS costuma ser UTC. Você pensa que executa às 2 da madrugada todos os dias, mas na verdade executa às 10 da manhã no horário de Pequim.
Verifique o fuso horário:
timedatectl
Se precisar mudar para o fuso horário de Xangai:
sudo timedatectl set-timezone Asia/Shanghai
Mas eu recomendo mais: planejar as tarefas de produção uniformemente em UTC e escrever claramente a conversão na documentação. Assim é menos provável haver confusão ao migrar para VPS de regiões diferentes.
Se as suas tarefas estão relacionadas à renovação de certificados, janelas de backup ou períodos de baixa carga do banco de dados, o fuso horário é especialmente importante. Para renovação de certificados, consulte: O que fazer quando a renovação do certificado Let's Encrypt falha na VPS.
Se você usa um systemd timer, não olhe apenas .timer.
Verifique o timer:
systemctl list-timers --all
systemctl status my-job.timer
Verifique o service que realmente executa:
systemctl status my-job.service
journalctl -u my-job.service --since "24 hours ago"
Um timer só é responsável por disparar; o que realmente executa é o service de mesmo nome ou o especificado. Os problemas comuns incluem:
- Apenas
.servicefoi habilitado, não.timer. .timerdisparou, mas.servicefalhou ao executar.OnCalendarestá escrito errado, então o próximo horário de disparo não é o que você pensa.WorkingDirectorynão foi definido no service, fazendo os caminhos relativos falharem.- O usuário de execução do service é diferente do usuário com quem você testou manualmente.
Um exemplo de service mais confiável:
[Unit]
Description=Daily app cleanup job
[Service]
Type=oneshot
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php artisan app:cleanup
Timer correspondente:
[Unit]
Description=Run app cleanup daily
[Timer]
OnCalendar=*-*-* 03:20:00
Persistent=true
[Install]
WantedBy=timers.target
Habilitar:
sudo systemctl daemon-reload
sudo systemctl enable --now app-cleanup.timer
Persistent=true funciona assim: depois que a máquina é desligada e perde o horário de execução, ele executa uma vez na próxima inicialização. Isso é muito útil para tarefas de backup, limpeza e sincronização, mas é preciso ter cuidado com tarefas como "enviar notificações", "cobrar tarifas" ou "reiniciar em lote" para evitar disparos concentrados após a recuperação.
O que mais assusta nas tarefas agendadas são duas coisas: não executar uma vez, ou executar duas vezes ao mesmo tempo.
Por exemplo, um script de backup que originalmente termina em 20 minutos, um dia o banco de dados cresce e ele leva 90 minutos, e como o cron dispara a cada hora, aparecem dois processos de backup disputando disco e rede.
Você pode usar flock para bloquear:
0 * * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /var/log/backup-cron.log 2>&1
Se a execução anterior ainda não terminou, a próxima é simplesmente ignorada.
Para backups de banco de dados, sincronização com armazenamento de objetos e compressão em lote, é melhor adicionar bloqueio. Para a estratégia de backup em si, consulte: Soluções de backup automático na VPS.
Se as suas tarefas rodam dentro de um contêiner Docker, o cron do host e o cron do contêiner são duas coisas distintas.
Erros comuns:
- No
crontab -ldo host não é possível ver as tarefas do contêiner. - Após reiniciar o contêiner, o cron dentro do contêiner não é iniciado.
- A imagem do contêiner não tem o pacote cron.
- As tarefas gravam no sistema de arquivos temporário do contêiner, e os arquivos são perdidos após a reconstrução do contêiner.
- O fuso horário dentro do contêiner é diferente do host.
Abordagens mais recomendadas:
- Tarefas simples: o cron do host chama
docker exec. - Tarefas do framework da aplicação: use o scheduler integrado do framework, mas garanta que o processo seja residente.
- Tarefas com altos requisitos de observabilidade: use um systemd timer ou um worker independente, em vez de colocá-las em um contêiner temporário.
Quando você encontrar "a tarefa agendada não executou", verifique nesta ordem:
systemctl status cronousystemctl status crond.crontab -lesudo crontab -l, confirme que o usuário não está errado.- Adicione
* * * * * date >> /tmp/cron-test.log 2>&1para um teste mínimo de disparo. - Verifique o log do cron ou
journalctl -u cron. - Altere o comando para caminho absoluto.
- Especifique
cd /path/to/projectou defina o diretório de trabalho dentro do script. - Verifique a permissão de execução do script e as permissões do usuário de destino.
- Grave a saída no log:
>> /var/log/job.log 2>&1. - Verifique
timedatectle o horário agendado da tarefa. - Se for um systemd timer, verifique tanto
.timerquanto.service. - Adicione
flockàs tarefas longas para evitar execuções sobrepostas.
Quando as tarefas agendadas da VPS não executam, o problema real normalmente não está na "expressão de tempo", mas no ambiente de execução: usuário diferente, PATH diferente, diretório diferente, permissões diferentes, fuso horário diferente.
Primeiro use uma tarefa mínima para provar que o agendador consegue disparar, depois divida o comando real em "caminho, usuário, diretório, permissões, logs" e confirme cada ponto um por um. Desde que os logs estejam visíveis, os caminhos sejam absolutos e o usuário esteja claro, a maioria dos problemas de cron e systemd timer pode ser localizada rapidamente.