O certificado venceu no feriado: renovação de SSL que não falha
O certificado tem 90 dias e a renovação é automática — até o dia em que não é. Estas são as quatro razões pelas quais o certbot para de renovar, e como descobrir antes do navegador avisar.
Equipe EasyOps Cloud · · 7 min de leitura
O certificado do Let's Encrypt vale 90 dias e se renova sozinho aos 60. Esse automatismo funciona tão bem que todo mundo esquece que ele existe — o que transforma a falha, quando acontece, em surpresa completa.
E a falha é silenciosa por natureza. O certbot tenta, erra, registra em um log
que ninguém lê e tenta de novo doze horas depois. Se a causa persistir, ele
repete por trinta dias até o certificado vencer. Só então o navegador estampa a
página vermelha — e costuma ser num feriado.
Primeiro: você sabe quando vence?
Antes de configurar qualquer coisa, saiba o estado atual:
sudo certbot certificatesA saída lista cada certificado, os domínios cobertos e quantos dias restam. Para
conferir o que o servidor realmente entrega — que pode ser diferente do que está
no disco, se o nginx não recarregou:
echo | openssl s_client -connect meusite.com.br:443 -servername meusite.com.br \
2>/dev/null | openssl x509 -noout -dates -subjectEssa distinção é o segundo erro mais comum desta lista inteira: o certbot
renovou, o arquivo novo está lá, e o nginx continua servindo o antigo em
memória.
O timer que parou
A renovação depende de um agendamento. Se ele não está ativo, nada acontece:
systemctl list-timers | grep -i certbot
systemctl status certbot.timerInstalações por snap e por apt criam timers diferentes, e máquina que já teve os
dois às vezes fica com o timer errado habilitado — ou com dois competindo.
Confira também se sobrou um cron antigo duplicando o trabalho:
ls -la /etc/cron.d/ | grep -i certbot
sudo crontab -l | grep -i certbotTer cron e timer ao mesmo tempo não é apenas redundante: dobra as tentativas contra o limite de emissão do Let's Encrypt, e é assim que se chega ao bloqueio temporário descrito mais adiante.
Para testar sem gastar cota, existe o ensaio:
sudo certbot renew --dry-runEste é o comando desta página. Ele executa o fluxo real contra o ambiente de teste, sem emitir certificado e sem consumir limite. Se ele passa, a renovação de verdade vai passar. Se falha, você acabou de descobrir com semanas de antecedência.
O hook que não recarrega o serviço
O certbot grava os arquivos novos e não sabe que o seu nginx precisa ser
avisado. Sem o gancho de recarga, o certificado novo fica no disco enquanto o
processo continua com o antigo — até alguém reiniciar por outro motivo, o que
mascara o problema por meses.
Com o plugin do nginx, isso é automático. Em modo webroot ou standalone, declare
explicitamente:
# /etc/letsencrypt/renewal/meusite.com.br.conf
[renewalparams]
authenticator = webroot
webroot_path = /var/www/html
deploy_hook = systemctl reload nginxOu de forma global, valendo para todos os certificados:
# /etc/letsencrypt/renewal-hooks/deploy/recarregar.sh
#!/usr/bin/env bash
set -euo pipefail
systemctl reload nginxsudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/recarregar.shPrefira deploy_hook a post_hook: o primeiro roda apenas quando um certificado
foi de fato renovado; o segundo roda em toda tentativa, inclusive nas que não
fizeram nada. Se outros serviços usam o mesmo certificado — um postfix, um dovecot,
um proxy próprio —, é neste script que eles entram.
A porta 80 que alguém fechou
O desafio HTTP-01 funciona assim: o Let's Encrypt acessa http://seudominio/.well-known/acme-challenge/...
para confirmar que você controla o domínio. Em HTTP, na porta 80, sempre.
É por isso que fechar a porta 80 "porque o site é todo HTTPS" quebra a renovação sessenta dias depois — tempo suficiente para ninguém associar as duas coisas. A porta 80 precisa continuar aberta, mesmo que só redirecione:
server {
listen 80;
server_name meusite.com.br;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}A ordem dos blocos importa: o location do desafio precisa vir antes do redirect,
senão o nginx manda o validador para HTTPS e o desafio falha.
Confirme que a porta está aberta nas duas camadas — na regra de firewall do
painel e no ufw da VM. Um firewall de perímetro liberando só a 443 produz
exatamente este sintoma, e o log do certbot só informa que o desafio não
respondeu.
Vale testar de fora, com o desafio simulado:
echo ok | sudo tee /var/www/html/.well-known/acme-challenge/teste
curl -I http://meusite.com.br/.well-known/acme-challenge/testePrecisa devolver 200. Se devolver 301, a ordem dos blocos está invertida.
Quando HTTP-01 não serve
Dois casos exigem o desafio por DNS:
- Certificado curinga (
*.meusite.com.br). O Let's Encrypt não emite curinga por HTTP-01, apenas por DNS-01. - Servidor sem porta 80 pública — ambiente interno, atrás de VPN, ou onde abrir a 80 não é aceitável.
O DNS-01 prova o controle criando um registro TXT. Com API do provedor de DNS,
continua automático:
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d meusite.com.br -d '*.meusite.com.br'sudo chmod 600 /etc/letsencrypt/cloudflare.iniA permissão 600 não é detalhe: o arquivo contém um token com poder de alterar o
seu DNS. Se o provedor não tiver plugin, existe --manual, mas ele não renova
sozinho — e um certificado que exige intervenção manual a cada 90 dias é um
incidente agendado.
O limite que ninguém lê até bater nele
O Let's Encrypt impõe cotas. A que mais dói é 5 certificados idênticos por semana: passou disso, você fica sem emitir por sete dias — inclusive para consertar o que estava tentando consertar.
É fácil chegar lá durante uma depuração: cada certbot --force-renewal que você
dispara "para testar" consome uma unidade. Por isso o --dry-run existe.
Se já bateu no limite e o certificado atual ainda é válido, não há emergência: espere. Se venceu, o caminho é um certificado com conjunto de domínios diferente (acrescente um subdomínio), que conta como outro certificado.
Mais de um servidor com o mesmo domínio
Tudo acima assume uma máquina. Com duas ou mais atrás de um balanceador, o HTTP-01
vira loteria: o validador do Let's Encrypt bate no domínio, o balanceador
encaminha para qualquer uma das máquinas, e o desafio só existe na que iniciou a
renovação. Falha em uma boa parte das tentativas, de forma intermitente — o pior
tipo de falha para diagnosticar.
Há três saídas, em ordem de simplicidade:
- Centralizar o TLS no balanceador. Só ele tem certificado; as VMs atrás falam HTTP puro na rede interna. É a arquitetura mais comum e elimina o problema pela raiz.
- Rotear `/.well-known/acme-challenge/` sempre para a mesma máquina. Uma regra fixa no balanceador resolve, e as demais recebem o certificado por cópia.
- Trocar para DNS-01. O desafio deixa de depender de qual servidor atende, já que a prova está no DNS. É a opção natural quando você já usa curinga.
Se optar por copiar o certificado entre máquinas, o deploy_hook é o lugar certo
para isso — e lembre que a chave privada viaja junto:
# /etc/letsencrypt/renewal-hooks/deploy/distribuir.sh
#!/usr/bin/env bash
set -euo pipefail
D=/etc/letsencrypt/live/meusite.com.br
for h in 10.0.0.12 10.0.0.13; do
rsync -a --chmod=600 "$D/" "root@$h:$D/"
ssh "root@$h" systemctl reload nginx
doneRestrinja essa rota na regra de firewall à rede interna. Uma cópia de chave privada trafegando é aceitável dentro da sua rede e inaceitável saindo dela.
Descobrir antes do cliente
O último elo é a vigilância. Um teste semanal que verifica dias restantes e avisa com folga:
#!/usr/bin/env bash
# /usr/local/bin/check-ssl.sh
LIMITE=21
for d in meusite.com.br loja.meusite.com.br; do
fim=$(echo | openssl s_client -connect "$d:443" -servername "$d" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
dias=$(( ( $(date -d "$fim" +%s) - $(date +%s) ) / 86400 ))
[ "$dias" -lt "$LIMITE" ] && echo "ALERTA: $d vence em $dias dias"
doneRepare que ele consulta a porta 443 de fora, e não o arquivo em disco. Isso pega os dois cenários de uma vez: a renovação que não aconteceu e a que aconteceu sem o serviço recarregar.
Agende com um systemd timer semanal e mande a saída para onde alguém realmente
olhe. Se optar por cron, vale rever por que tarefas de cron não rodam — o script
acima depende de openssl e date no PATH, que é justamente o que o cron não
garante.
O resumo
Configure uma vez e verifique três coisas: certbot renew --dry-run passa, o deploy_hook
recarrega o serviço, e a porta 80 continua aberta. Se as três respostas forem
sim, a renovação vai acontecer sem você.
Para quem está montando o domínio agora, o guia de apontamento e SSL cobre o DNS e a emissão inicial — a renovação, que é a parte que falha meses depois, é o que este artigo resolve.