Skip to content

Runbook: TLSCertExpiresSoon

Alerte : TLSCertExpiresSoon (PrometheusRule, severity warning à J-30, critical à J-7)

Symptôme :

Un certificat TLS (ingress, Keycloak, Harbor) expire dans moins de 30 jours et n'a pas été renouvelé automatiquement.

Severity : 🟡 warning à J-30, 🔴 critical à J-7


Diagnostic

1. Identifier le certificat qui expire

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

# Via cert-manager
kubectl get certificate -A \
  -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,READY:.status.conditions[0].status,NOT_AFTER:.status.notAfter

# Via les secrets TLS directement
for s in $(kubectl get secret -A -o json | jq -r '.items[] | select(.type=="kubernetes.io/tls") | "\(.metadata.namespace)/\(.metadata.name)"'); do
  NS=$(echo $s | cut -d/ -f1)
  NAME=$(echo $s | cut -d/ -f2)
  NOT_AFTER=$(kubectl get secret -n $NS $NAME -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  echo "$NS/$NAME -> $NOT_AFTER"
done | sort

2. Vérifier cert-manager est sain

kubectl get pod -n cert-manager
kubectl logs -n cert-manager -l app=cert-manager --tail=50

3. Vérifier le Issuer/ClusterIssuer

kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod | tail -20

Causes fréquentes + fix

Cause Symptôme Fix
ACME rate limit Let's Encrypt too many certificates already issued Utiliser staging pendant les tests + attendre 1 semaine.
HTTP-01 challenge bloqué self-check failed Vérifier que l'Ingress acme-http-solver est reachable depuis internet (80/TCP).
DNS-01 challenge non configuré Pour wildcard certs Configurer le DNS provider (Cloudflare, Route53) dans ClusterIssuer.
Secret TLS corrompu failed to parse certificate kubectl delete secret <name> → cert-manager recrée.
Certificate request bloqué CertificateRequest en Pending Voir kubectl describe certificaterequest <name>.
Lost Let's Encrypt account key 401 Unauthorized Recréer le ClusterIssuer avec nouveau privateKey.

Fix d'urgence (< 10 min)

Forcer un renouvellement manuel (cert-manager)

# Supprime le cert, cert-manager le recrée
kubectl delete certificate -n <namespace> <cert-name>
# Ou annotation pour force renew
kubectl annotate certificate -n <namespace> <cert-name> \
  cert-manager.io/issue-temporary-certificate=true --overwrite

Si Let's Encrypt prod rate-limited — temporairement switcher vers staging

Jamais en prod long terme — uniquement pendant investigation.


Fix pérenne (R02)

  1. cert-manager auto-renewal doit être sain :
  2. Tous les certs doivent avoir renewBefore: 720h (30 jours avant expiration)
  3. Monitoring alertes précoces : PrometheusRule à J-30 pas J-7
  4. Wildcard cert via DNS-01 pour éviter les issues HTTP-01 par subdomain

Jamais : copier un cert en live avec kubectl create secret tls. Toujours via cert-manager + Git.


Prévention

  • PrometheusRule précoce : alerte à J-30 via probe_ssl_earliest_cert_expiry (blackbox-exporter)
    - alert: TLSCertExpiresSoon
      expr: probe_ssl_earliest_cert_expiry{} - time() < 30 * 86400
      severity: warning
    - alert: TLSCertExpiresCritical
      expr: probe_ssl_earliest_cert_expiry{} - time() < 7 * 86400
      severity: critical
    
  • Playwright test hebdomadaire : vérifier HTTPS root + expiry > 7j
  • Documentation : liste des certs critiques + qui les renouvelle

Lessons learned

  • Let's Encrypt a des rate limits stricts (50 certs/semaine/domain, 5 duplicates/week). Utiliser le staging pour tous les tests.

Liens utiles