El problema que resolvemos#
Tuve un certificado SSL que venció en producción hace poco. No fue culpa de Let’s Encrypt, sino de una configuración deficiente de Traefik. Los certificados se renovaban, pero la cadena de confianza no se validaba correctamente y los navegadores mostraban advertencias.
Después de varias iteraciones, logré una configuración estable. Aquí documento cómo hacerlo bien desde el principio.
¿Por qué renovar antes del vencimiento?#
Let’s Encrypt emite certificados válidos por 90 días. Si esperas al último momento, cualquier problema en la renovación te deja sin certificado válido. Traefik permite configurar una ventana de renovación anticipada. Yo lo puse en 30 días antes del vencimiento.
Configuración de Traefik#
La clave está en el archivo traefik.yml o en los labels de Docker. Aquí muestro la configuración YAML que funciona:
certificatesResolvers:
letsencrypt:
acme:
email: tu-email@ejemplo.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
renewBefore: 720h # 30 días antes del vencimientoEl parámetro renewBefore es crítico. Lo especifico en horas: 720 horas = 30 días. Traefik verificará cada hora si el certificado necesita renovación.
Si usas Docker Compose, lo configuras así en los labels del servicio:
labels:
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.routers.myapp.tls.domains[0].main=midominio.com"
- "traefik.http.routers.myapp.tls.domains[0].sans=www.midominio.com"Almacenamiento seguro del certificado#
El archivo acme.json contiene la clave privada del certificado. Debe estar protegido:
chmod 600 /letsencrypt/acme.jsonLo monto como volumen en Docker con permisos restrictivos. Si usas un servidor dedicado, asegúrate de que solo Traefik pueda leerlo.
Validación de la cadena de confianza#
Aquí está el punto que muchos olvidan. Traefik descarga el certificado y la cadena intermedia de Let’s Encrypt, pero debes verificar que todo sea correcto:
# Inspecciona el certificado
openssl x509 -in /letsencrypt/acme.json -text -noout
# Valida la cadena completa
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /ruta/al/certificado.crtEn mi caso, añadí un pequeño script que ejecuto cada semana para validar:
#!/bin/bash
# Extrae el certificado del acme.json
python3 << 'EOF'
import json
with open('/letsencrypt/acme.json') as f:
data = json.load(f)
for account in data.values():
for cert in account.get('Certificates', []):
print(cert['certificate'])
EOFLuego verifico con openssl que la cadena sea completa y válida.
Monitoreo y alertas#
No basta con configurar la renovación. Necesito saber si algo falla. Reviso los logs de Traefik regularmente:
docker logs traefik 2>&1 | grep -i "certificate\|acme\|renew"Si el certificado no se renueva en 30 días, algo está mal. Causas comunes:
- El archivo
acme.jsonno tiene permisos de escritura - El desafío HTTP no funciona (puerto 80 bloqueado)
- DNS no resuelve correctamente
Validación después de la renovación#
Una vez renovado el certificado, valido manualmente:
# Conecta a tu dominio y extrae el certificado
echo | openssl s_client -servername midominio.com -connect midominio.com:443 2>/dev/null | openssl x509 -noout -datesEsto me muestra las fechas de validez actuales. Debe mostrar una fecha de vencimiento 90 días en el futuro.
Conclusión#
La rotación automática de certificados no es complicada, pero requiere atención a los detalles. Los puntos clave son:
- Configura
renewBeforecon suficiente anticipación (30 días mínimo) - Asegura permisos correctos en
acme.json - Valida la cadena de confianza regularmente
- Monitorea los logs de Traefik
Con esta configuración, llevo meses sin problemas de certificados vencidos. El sistema renueva sin intervención manual y los navegadores no muestran advertencias.