From 64422f274dbd3df5acd8b2b82a30f674197cad86 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Wed, 1 Jul 2026 11:50:54 +0100 Subject: [PATCH] kube/certs: use Let's Encrypt's recommended retry schedule (#20292) Replace the doubling backoff (1m, 2m, 4m, ...) with LE's recommended 1m, 10m, 100m, daily. The old schedule burned retry attempts inside the rate-limit window without speeding recovery. Updates #20288 Updates #19895 Signed-off-by: chaosinthecrd --- kube/certs/certs.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/kube/certs/certs.go b/kube/certs/certs.go index 551b9178f..e76d6e188 100644 --- a/kube/certs/certs.go +++ b/kube/certs/certs.go @@ -94,6 +94,16 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig) return nil } +// retrySchedule is the wait between successive failed issuance attempts, +// following LE's recommended schedule. +// https://letsencrypt.org/docs/integration-guide/#retrying-failures +var retrySchedule = []time.Duration{ + 1 * time.Minute, + 10 * time.Minute, + 100 * time.Minute, + 24 * time.Hour, +} + // runCertLoop: // - calls localAPI certificate endpoint to ensure that certs are issued for the // given domain name @@ -103,13 +113,8 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig) // Note that renewal check also happens when the node receives an HTTPS request and it is possible that certs get // renewed at that point. Renewal here is needed to prevent the shared certs from expiry in edge cases where the 'write' // replica does not get any HTTPS requests. -// https://letsencrypt.org/docs/integration-guide/#retrying-failures func (cm *CertManager) runCertLoop(ctx context.Context, domain string) { - const ( - normalInterval = 24 * time.Hour // regular renewal check - initialRetry = 1 * time.Minute // initial backoff after a failure - maxRetryInterval = 24 * time.Hour // max backoff period - ) + const normalInterval = 24 * time.Hour // regular renewal check if err := cm.waitForCertDomain(ctx, domain); err != nil { // Best-effort, log and continue with the issuing loop. @@ -153,15 +158,11 @@ func (cm *CertManager) runCertLoop(ctx context.Context, domain string) { nextInterval = normalInterval } else { retryCount++ - // Calculate backoff: initialRetry * 2^(retryCount-1) - // For retryCount=1: 1min * 2^0 = 1min - // For retryCount=2: 1min * 2^1 = 2min - // For retryCount=3: 1min * 2^2 = 4min - backoff := initialRetry * time.Duration(1<<(retryCount-1)) - if backoff > maxRetryInterval { - backoff = maxRetryInterval + idx := retryCount - 1 + if idx >= len(retrySchedule) { + idx = len(retrySchedule) - 1 } - nextInterval = backoff + nextInterval = retrySchedule[idx] cm.logf("Error refreshing certificate for %s (retry %d): %v. Will retry in %v\n", domain, retryCount, err, nextInterval) }