Renovar SSL automaticamente com Certbot e cron no Linux
Aprenda a renovar SSL automaticamente com Certbot e cron no Linux: timer systemd, hook de reload do Nginx, monitoramento e troubleshooting de produção.
Certificados emitidos pelo Let’s Encrypt têm validade de 90 dias — uma decisão intencional do projeto pra forçar automação e reduzir janela de exposição em caso de comprometimento. Renovação manual a cada 60-80 dias é insustentável em produção: basta uma viagem, um plantão sobrecarregado ou um servidor esquecido pra TLS expirar e o site começar a retornar ERR_CERT_DATE_INVALID no navegador dos clientes.
Este tutorial é pra sysadmins e desenvolvedores que já têm Certbot instalado e ao menos um certificado emitido, e querem garantir que a renovação rode sem intervenção, recarregue o servidor web sem downtime e avise se algo quebrar. Vamos cobrir as duas abordagens — cron clássico e systemd timer — escolher quando usar cada uma, configurar deploy hooks pra reload do Nginx/Apache e montar um monitoramento básico.
Tempo estimado de execução: 15 a 25 minutos, dependendo de quantos domínios você gerencia e se vai configurar alertas customizados.
Pré-requisitos
Ubuntu 22.04 LTS ou superior (ou Debian 11+, Rocky 9+), Certbot 2.0+ instalado, ao menos um certificado já emitido em /etc/letsencrypt/live/, acesso sudo e Nginx ou Apache ativo servindo o domínio. Porta 80 deve estar aberta no firewall pra desafio HTTP-01 (ou DNS configurado pra DNS-01).
Confirme a versão do Certbot e a presença de pelo menos um certificado antes de prosseguir:
certbot --version
sudo certbot certificates
A saída de certbot certificates deve listar cada certificado com nome, domínios cobertos e data de expiração. Se nada aparece, emita um primeiro certificado antes de configurar a automação — esta documentação assume que a etapa de emissão inicial já foi feita.
/etc/letsencrypt/live/ /etc/letsencrypt/renewal-hooks/ /etc/letsencrypt/renewal/ /var/log/letsencrypt/ Verificando se o timer systemd já está ativo
Em distros modernas o pacote certbot instala automaticamente uma unit systemd que cuida da renovação — antes de mexer em cron, confirme se ela já está rodando. A maioria das instalações Ubuntu 22.04+ não precisa de cron manual nenhum.
Liste timers ativos e procure pelo certbot:
systemctl list-timers --all | grep -i certbotSe aparecer uma linha como certbot.timer ... loaded active waiting, o timer já está agendado. A próxima execução vem na coluna NEXT. Pule pra seção de deploy hook se for esse o caso.
Inspecione a configuração do timer pra entender a frequência:
systemctl cat certbot.timerA unit padrão dispara duas vezes por dia (00:00 e 12:00) com RandomizedDelaySec=43200 — ou seja, um atraso aleatório de até 12 horas. Isso espalha as renovações de todos os usuários do Let’s Encrypt no mundo, evitando concentração de carga.
Verifique o serviço que o timer dispara:
systemctl cat certbot.serviceO ExecStart típico é /usr/bin/certbot -q renew — o -q (quiet) suprime output em casos de “nothing to renew”, evitando ruído no log. Quando uma renovação acontece, o output completo vai pro journal.
Configurando renovação via cron tradicional
Se você está em uma distro antiga, em um servidor sem systemd, ou simplesmente prefere cron, use esta abordagem. Em distros modernas, desabilite o timer antes pra evitar duas execuções concorrentes.
Desabilite o timer systemd se for usar cron:
sudo systemctl disable --now certbot.timerEsse comando para o timer e remove o symlink que o ativa no boot. Você pode reativar a qualquer momento com enable --now.
Edite o crontab do root e adicione a entrada de renovação:
sudo crontab -eAdicione:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3,15 * * * /usr/bin/certbot renew --quietEssa linha roda às 03:00 e 15:00 todo dia. O Certbot só renova certificados com menos de 30 dias restantes — rodar duas vezes ao dia garante uma segunda tentativa se a primeira falhar por instabilidade temporária. O PATH explícito é obrigatório: cron herda um ambiente mínimo e comandos sem caminho absoluto falham silenciosamente.
Confirme que o cron foi registrado:
sudo crontab -lA linha deve aparecer. O cron lê o arquivo automaticamente — não precisa reiniciar nenhum serviço.
Evite agendar renovações em horários redondos como 00:00 ou 12:00 sem jitter — milhares de servidores fazem o mesmo e a API do Let’s Encrypt pode rate-limitar. Use horários ímpares (03:17, 15:43) ou adicione sleep $((RANDOM \% 3600)) antes do certbot pra randomizar.
Configurando deploy hook pra reload sem downtime
Renovar o certificado não basta — o Nginx ou Apache continua usando o cert antigo carregado em memória até receber sinal pra recarregar. Deploy hooks resolvem isso elegantemente.
Crie o hook de reload do Nginx:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'
#!/bin/bash
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shQualquer script executável em /etc/letsencrypt/renewal-hooks/deploy/ roda após renovação bem-sucedida — e só após. Diferente do --post-hook, que roda sempre, o deploy hook é skippado se nenhum certificado foi efetivamente renovado, evitando reloads desnecessários do Nginx.
Pra Apache, use o equivalente:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh > /dev/null <<'EOF'
#!/bin/bash
systemctl reload apache2
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-apache.shsystemctl reload (não restart) recarrega a configuração sem dropar conexões ativas — workers existentes terminam suas requisições enquanto novos workers já usam o cert renovado.
Teste o pipeline completo em modo dry-run:
sudo certbot renew --dry-runO --dry-run simula renovação contra o servidor staging do Let’s Encrypt — não consome quota, não substitui o certificado real, mas executa todos os hooks. Confira o output: ao final deve aparecer Congratulations, all simulated renewals succeeded e o hook deve ter sido executado.
Se você roda Nginx e HAProxy no mesmo servidor e cada um usa certificados diferentes, configure o hook dentro do arquivo de renewal específico em /etc/letsencrypt/renewal/dominio.conf na seção [renewalparams] com renew_hook = systemctl reload haproxy. Hooks no diretório global rodam pra todo cert renovado.
Monitorando renovação
Automação sem observabilidade é uma armadilha — descobrir que o cert expirou pelo erro do navegador é o pior cenário possível. Monte um check ativo que avise antes.
Crie um script de check de expiração:
sudo tee /usr/local/bin/check-ssl-expiry.sh > /dev/null <<'EOF'
#!/bin/bash
THRESHOLD_DAYS=20
ALERT_EMAIL="[email protected]"
for cert in /etc/letsencrypt/live/*/cert.pem; do
domain=$(basename "$(dirname "$cert")")
end_date=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)
end_epoch=$(date -d "$end_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (end_epoch - now_epoch) / 86400 ))
if [ "$days_left" -lt "$THRESHOLD_DAYS" ]; then
echo "ALERTA: $domain expira em $days_left dias" | \
mail -s "SSL expirando: $domain" "$ALERT_EMAIL"
fi
done
EOF
sudo chmod +x /usr/local/bin/check-ssl-expiry.shAgende o script pra rodar diariamente via cron ou systemd timer. Se um certificado entra na zona de menos de 20 dias, você recebe email — significa que a renovação automática falhou e precisa investigação manual.
Agende o check diário:
echo "30 8 * * * /usr/local/bin/check-ssl-expiry.sh" | sudo tee -a /etc/crontabRoda toda manhã às 08:30. Combine com integração de alertas (Slack webhook, Discord, ou seu sistema de métricas) substituindo a chamada mail por curl pro webhook.
Verificação final
Rode os comandos abaixo pra confirmar que toda a configuração está ativa:
sudo certbot renew --dry-run
systemctl list-timers --all | grep certbot
ls -la /etc/letsencrypt/renewal-hooks/deploy/
sudo /usr/local/bin/check-ssl-expiry.sh
O dry-run deve passar sem erros. O timer (se você optou por systemd) deve estar ativo. Os hooks devem ser executáveis. O script de check não deve emitir alerta — se emitir, o cert está em janela crítica e você precisa investigar logs em /var/log/letsencrypt/letsencrypt.log.
Resolução de problemas
”Failed authorization procedure” no renew
Causa comum: porta 80 bloqueada por firewall ou redirecionada pra 443 sem exceção pro path /.well-known/acme-challenge/. Certbot precisa servir o desafio HTTP-01 nessa rota. Confirme com curl http://seu-dominio/.well-known/acme-challenge/test — deve retornar 404 do seu servidor, não erro de conexão. Se você força redirect global pra HTTPS, adicione exceção pra esse path no Nginx.
”Hook command exited with code 1”
O script do hook retornou erro. Rode manualmente pra debugar: sudo bash /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh. Erros típicos: nginx config inválida (nginx -t primeiro), serviço não rodando, ou permissões erradas no script.
Timer aparece ativo mas nunca renova
Verifique horário do servidor com timedatectl — se o relógio está incorreto, o timer dispara mas o certbot rejeita a operação. Sincronize com sudo systemctl restart systemd-timesyncd. Outro caso: o cert ainda não está em janela de 30 dias antes de expirar; certbot ignora silenciosamente. Force com --force-renewal apenas se confirmou outras causas.
Próximos passos
Com renovação automatizada e monitorada, considere estes próximos passos pra aumentar a robustez do seu setup TLS:
- Configurar OCSP stapling no Nginx — reduz latência da validação do cert no navegador e diminui carga nos servidores OCSP do Let’s Encrypt.
- Avaliar wildcard certificates com DNS-01 challenge — útil se você gerencia muitos subdomínios; requer plugin DNS específico do seu provedor.
- Implementar HSTS preload após confirmar estabilidade da renovação — comprometedor de reverter, então só ative quando a automação rodou por pelo menos 2 ciclos completos.
- Centralizar logs de renovação em um sistema de observabilidade — útil em fleets de servidores onde checar cada máquina individualmente não escala.
Se você está colocando isso em produção, uma VPS Hostini já vem com Ubuntu LTS atualizado, SSH endurecido por padrão e tráfego com proteção DDoS — base sólida pra automações de TLS sem surpresas no kernel ou na rede.
Perguntas frequentes
Por que meu cron de renovação não está rodando mesmo configurado?
Cron exige PATH explícito — o ambiente do cron não herda o PATH do shell interativo. Adicione `PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` no topo do crontab e use caminhos absolutos pro certbot (`/usr/bin/certbot`). Verifique também `/var/log/syslog | grep CRON` pra confirmar execução.
Posso forçar a renovação antes dos 30 dias pra testar?
Use `certbot renew --dry-run` pra simular sem consumir quota do Let's Encrypt. Pra forçar renovação real, rode `certbot renew --force-renewal` — mas cuidado: o Let's Encrypt limita a 5 certificados duplicados por semana e renovações forçadas contam pro limite.
Qual a diferença entre cron tradicional e systemd timer pra Certbot?
O pacote certbot moderno (Ubuntu 22.04+, Debian 12+) já instala um systemd timer (`certbot.timer`) que roda 2x por dia com jitter aleatório. Cron tradicional ainda funciona mas o timer tem vantagem: persistente (roda no boot se perdeu a janela), randomização anti-thundering-herd, e logs centralizados no journalctl.
Como faço o Nginx ou Apache recarregar sem downtime após a renovação?
Use o deploy hook: `certbot renew --deploy-hook 'systemctl reload nginx'`. O hook só executa quando um certificado foi efetivamente renovado, evitando reloads desnecessários. Reload (não restart) recarrega config sem dropar conexões ativas — zero downtime.
O renew falhou com erro de rate limit. Quanto tempo até eu poder tentar de novo?
O Let's Encrypt aplica vários limites: 50 certificados por domínio raiz por semana, 5 duplicados por semana, 5 falhas por hora por conta+hostname. A janela é deslizante — espere até que a request mais antiga saia da janela. Use staging (`--staging`) pra testar sem afetar quota de produção.
Como sou notificado se a renovação falhar silenciosamente?
Configure email de contato no Certbot (`--email`) — o Let's Encrypt envia aviso 20, 10 e 1 dia antes da expiração. Pra monitoramento ativo, adicione um script que checa `openssl x509 -enddate -noout -in /etc/letsencrypt/live/dominio/cert.pem` e dispara alerta se restar menos de 20 dias.