Tutoriales de VPS
Qué hacer cuando falla la renovación del certificado Let's Encrypt en un VPS (2026): lista de verificación de certbot, Cloudflare, DNS-01 y puerto 80
Los certificados Let's Encrypt solo tienen 90 días de validez, y certbot.timer puede parecer normal aunque la renovación falle. Este artículo organiza una lista de verificación para fallos de renovación de certificados HTTPS en VPS en este orden: certbot renew --dry-run, puerto 80, challenge de Nginx, proxy de Cloudflare, DNS-01, deploy hook y monitoreo de vencimiento.

El fallo de renovación de un certificado Let's Encrypt normalmente no empieza el día en que vence.
A menudo ya ha fallado varias veces mucho antes, solo que no lo has visto. Cuando el navegador empieza a avisar de que el certificado ha caducado, los usuarios no pueden abrir HTTPS, fallan los callbacks de pago y los clientes de API dan errores TLS, la avería ya se ha convertido en un incidente en producción.
Lo más problemático es que muchos usuarios de VPS creen que, por haber instalado certbot y tener certbot.timer en el sistema, la renovación automática ya está garantizada. En realidad, el timer solo significa que se ejecutará periódicamente, no que cada renovación vaya a tener éxito.
Este artículo sigue un orden de diagnóstico: primero confirmar qué certificados hay, luego ejecutar un dry-run, después revisar el puerto 80, Nginx, Cloudflare, DNS-01, el deploy hook y el monitoreo.
Primer comando:
sudo certbot certificates
Fíjate en:
- Certificate Name
- Domains
- Expiry Date
- Certificate Path
- Key Type
Si aquí el certificado muestra que le quedan solo diez y pocos días o incluso unos pocos días, no esperes a que la renovación automática "se arregle sola". Ejecuta directamente un dry-run.
sudo certbot renew --dry-run
La documentación oficial de Certbot también recomienda usar un dry-run para probar la ruta de renovación automática. Recorre un proceso de renovación simulado sin sustituir el certificado real, por lo que es el primer comando de diagnóstico más seguro.
Si el dry-run tiene éxito, al menos indica que el método de validación actual, la colaboración del servidor web y el DNS o el challenge están básicamente bien. Si falla, sigue investigando según el mensaje de error.
Primero mira el timer:
systemctl list-timers | grep certbot
systemctl status certbot.timer
Luego mira los registros:
journalctl -u certbot --since "7 days ago" --no-pager
journalctl -u certbot.timer --since "7 days ago" --no-pager
Según el método de instalación, puede usar cron o un timer de systemd. También puedes comprobar:
ls /etc/cron.d/
ls /etc/cron.daily/
Un error común es: el timer está activo, pero cada ejecución falla; o el sistema usa la versión snap de certbot, cuya ubicación de registros no es exactamente igual que la de la versión instalada por el gestor de paquetes. No mires solo "si hay timer", mira el dry-run y los registros.
El challenge HTTP-01 de Let's Encrypt accede a:
http://你的域名/.well-known/acme-challenge/<TOKEN>
La documentación oficial de Let's Encrypt indica claramente que HTTP-01 solo puede completarse en el puerto 80. No se puede especificar 8080, 8888 ni ningún otro puerto.
Por eso debes confirmar:
- Los registros A / AAAA del dominio apuntan a este VPS
- El grupo de seguridad / firewall del VPS permite el puerto 80
- Nginx/Apache escucha en el puerto 80
.well-known/acme-challenge/no está redirigido o bloqueado por error- El proxy de Cloudflare no ha cambiado la ruta de validación
Primero comprueba desde el exterior si el puerto 80 es accesible:
curl -I http://example.com
Luego prueba la ruta del challenge. Puedes crear temporalmente un archivo de prueba:
sudo mkdir -p /var/www/html/.well-known/acme-challenge
printf 'ok\n' | sudo tee /var/www/html/.well-known/acme-challenge/test
curl http://example.com/.well-known/acme-challenge/test
Si devuelve ok, significa que esta ruta al menos es accesible desde la red pública. Elimínalo después de la prueba:
sudo rm /var/www/html/.well-known/acme-challenge/test
Si no es accesible, sigue revisando la configuración de Nginx y el firewall.
Muchos sitios fuerzan la redirección de HTTP a HTTPS:
return 301 https://$host$request_uri;
Esto normalmente no es problema; Let's Encrypt puede seguir un cierto número de redirecciones HTTP/HTTPS. Pero si rediriges .well-known al directorio equivocado, a una página de inicio de sesión, a otro dominio, o si el CDN/WAF devuelve 403, fallará.
Se recomienda reservar una ruta separada para el challenge:
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
Después de cambiarlo, prueba primero:
sudo nginx -t
sudo systemctl reload nginx
No reinicies directamente. Deja que nginx -t pase primero y luego haz reload.
Muchos sitios en VPS están detrás de Cloudflare. Cloudflare en sí no es el problema, pero añade una capa más al diagnóstico.
Errores comunes:
- Tras activar el proxy naranja en el registro DNS, las solicitudes de validación llegan primero a Cloudflare y luego al origen
- El puerto 80 del origen no está abierto, pero la caché o las reglas de Cloudflare hacen que el acceso superficial parezca normal
- WAF / Redirect Rule / Page Rule afectan a
/.well-known/acme-challenge/ - El modo SSL de Cloudflare y el estado del certificado del origen se mezclan en el diagnóstico
Si usas HTTP-01, primero confirma que el puerto 80 del origen es realmente accesible. Si es necesario, puedes cambiar temporalmente los registros correspondientes a DNS only (nube gris) para probar, o pasar a DNS-01.
Si el sitio está detrás de Cloudflare a largo plazo, necesita un certificado comodín o no es conveniente abrir el puerto 80, DNS-01 suele ser más adecuado.
El challenge DNS-01 de Let's Encrypt crea un registro TXT en el DNS:
_acme-challenge.example.com TXT "..."
Es adecuado para estas situaciones:
- Necesitas un certificado comodín
*.example.com - No es conveniente abrir el puerto 80 en el origen
- El sitio está detrás de Cloudflare / un proxy inverso
- Varios servidores comparten el mismo proceso de certificado
Pero el problema central de DNS-01 es el tiempo de propagación. Que veas el registro TXT en local no significa que Let's Encrypt pueda verlo desde sus nodos de validación.
Puedes comprobarlo así:
dig TXT _acme-challenge.example.com @1.1.1.1
dig TXT _acme-challenge.example.com @8.8.8.8
También puedes consultar los NS autoritativos:
dig example.com NS +short
dig TXT _acme-challenge.example.com @ns1.example-dns.com
Si varios nameservers devuelven resultados inconsistentes, la renovación puede fallar de forma aleatoria. Este problema es común en algunos proveedores de DNS, DNS primario/secundario y escenarios de actualización manual de registros TXT.
Al usar un token de API de DNS, presta atención a los permisos:
- Da solo permiso de edición DNS para la zona de destino
- No pongas una clave de API global en el directorio raíz web
- Establece los permisos del archivo de credenciales en 600
Por ejemplo:
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
La documentación oficial de Let's Encrypt indica que DNS-01 puede emitir certificados comodín, mientras que HTTP-01 no puede.
Así que si quieres:
*.example.com
No sigas dándole vueltas al puerto 80; deberías usar DNS-01.
Al usar el plugin de Cloudflare, el proceso general es:
sudo apt install python3-certbot-dns-cloudflare
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d example.com \
-d '*.example.com'
Según el sistema, la versión snap de certbot y los distintos paquetes de plugins DNS, los nombres pueden variar. Antes de ejecutarlo de verdad, comprueba primero cómo está instalado tu certbot actual:
which certbot
certbot --version
Hay otro caso: certbot renovó correctamente, pero Nginx no hizo reload y sigue sirviendo con el certificado antiguo.
Primero mira la fecha de vencimiento del archivo de certificado:
sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates
Luego mira el certificado que realmente se devuelve en línea:
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
Si el archivo local es nuevo y el de línea sigue siendo el antiguo, haz reload del servicio web.
sudo nginx -t
sudo systemctl reload nginx
Una forma mejor es configurar un deploy hook para que haga reload automáticamente tras una renovación exitosa:
sudo certbot renew --deploy-hook "systemctl reload nginx"
También puedes poner el hook en:
/etc/letsencrypt/renewal-hooks/deploy/
Nota: el deploy hook solo debe hacer acciones ligeras y deterministas, como reload de nginx. No escribas scripts de despliegue complejos dentro de él.
Tras ver un fallo, algunas personas ejecutan una y otra vez:
sudo certbot renew --force-renewal
Esto no es un buen hábito. Let's Encrypt tiene límites de tasa, y los fallos repetidos pueden impedirte seguir emitiendo durante un tiempo. Al diagnosticar, prioriza:
sudo certbot renew --dry-run
Si necesitas probar el proceso con frecuencia, puedes usar el entorno staging para no consumir la cuota de producción:
sudo certbot certonly --staging -d example.com
Cuando la configuración esté realmente corregida, vuelve al entorno de producción.
No dependas solo de certbot. Al menos debe haber un recordatorio externo o local.
Comprobación local sencilla:
DOMAIN=example.com
END_DATE=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
END_TS=$(date -d "$END_DATE" +%s)
NOW_TS=$(date +%s)
DAYS_LEFT=$(( (END_TS - NOW_TS) / 86400 ))
echo "$DOMAIN certificate expires in $DAYS_LEFT days"
Si queda menos de 14 días, deberías alertar. En producción se recomienda más usar monitoreo externo, porque si el propio DNS, la red o la programación de tareas del VPS fallan, el script local también puede fallar junto con ellos.
Cuando falle la renovación de Let's Encrypt, sigue este orden:
-
sudo certbot certificatesver certificados y fechas de vencimiento -
sudo certbot renew --dry-runreproducir el error de renovación -
systemctl list-timers | grep certbotrevisar las tareas automáticas -
journalctl -u certbot --since "7 days ago"revisar los registros - HTTP-01: confirmar que el puerto 80 es accesible desde la red pública
- HTTP-01: confirmar que
/.well-known/acme-challenge/es accesible - Nginx: hacer reload tras
nginx -t - Cloudflare: revisar nube naranja, WAF, Redirect Rule, modo SSL
- DNS-01: confirmar que el TXT se ve en varios resolvers y NS autoritativos
- Certificado comodín: usar DNS-01, no HTTP-01
- Renovación exitosa pero certificado antiguo en línea: revisar deploy hook / reload
- No repetir
--force-renewal; priorizar dry-run y staging - Configurar un recordatorio de vencimiento a 14 días
El fallo de renovación de certificados no es magia. La mayoría de los problemas caen en cuatro categorías: las solicitudes de validación no llegan, el TXT de DNS no se ha propagado, la renovación tuvo éxito pero el servicio no hizo reload, y nadie monitorea la fecha de vencimiento. Si pones en orden el dry-run, la ruta del challenge, DNS-01 y el deploy hook, el certificado HTTPS no te dará un susto cada 90 días.