Problem
SANCertManager.GetCertificate (internal/server/san_cert_manager.go) has an availability hole for deploy-registered domains in the final 24h of a certificate's life:
if cert != nil && cert.Certificate != nil {
if time.Until(cert.NotAfter) > 24*time.Hour && !directoryMismatch {
return cert.Certificate, nil
}
if isRegistered {
// logs "will reprovision" and FALLS THROUGH
} else if time.Until(cert.NotAfter) > 0 {
// dynamic domains serve the still-valid cert; the renewer rotates it
return cert.Certificate, nil
}
}
if isRegistered {
return m.provisionCertificate(hello.Context(), domain) // synchronous ACME order on the handshake
}
For a registered domain within 24h of expiry, the handshake blocks on a synchronous ACME order — and if that order fails (ACME outage, rate limit, DNS-01 propagation delay), the handshake errors even though a still-valid certificate is in memory. The dynamic path directly above it already does the right thing: serve the valid cert, let the background renewer rotate it.
Reaching this state at all means proactive renewal (ARI window / ⅔-lifetime, domain_renewal.go) has been failing for weeks — exactly the scenario (ACME outage, quarantined batch-mates deferring renewal into the last 7 days via quarantineCompactionWindow) in which the synchronous retry is most likely to also fail. The last 24h before expiry is when the proxy should be squeezing every drop out of the cert it holds, not gambling handshakes on a live order.
Secondary effect: waiters on the in-flight provisioning single-flight get getCertForDomain (which ignores expiry) and are fine; only the triggering handshake eats the error. So the failure is intermittent per-connection — worse to diagnose.
Expected
Registered domains mirror the dynamic path: while time.Until(cert.NotAfter) > 0, serve the held certificate and request replacement asynchronously (the renewer and pending-batch machinery already exist). Synchronous handshake provisioning remains only for domains with no usable certificate at all (first issuance) — that is the one case where blocking the handshake buys anything.
The directoryMismatch reprovision from #100 should follow the same rule: serve what we hold, replace in the background.
Acceptance criteria
- GIVEN a registered domain whose certificate expires in <24h WHEN ACME is unreachable THEN handshakes keep serving the held certificate until its actual
NotAfter
- GIVEN the same domain WHEN a handshake arrives THEN a replacement order is queued asynchronously without blocking the handshake
- GIVEN a registered domain with no certificate THEN the synchronous first-issuance path is unchanged
Found during the SAN grouping / primary-domain-validity audit (2026-08-13).
Problem
SANCertManager.GetCertificate(internal/server/san_cert_manager.go) has an availability hole for deploy-registered domains in the final 24h of a certificate's life:For a registered domain within 24h of expiry, the handshake blocks on a synchronous ACME order — and if that order fails (ACME outage, rate limit, DNS-01 propagation delay), the handshake errors even though a still-valid certificate is in memory. The dynamic path directly above it already does the right thing: serve the valid cert, let the background renewer rotate it.
Reaching this state at all means proactive renewal (ARI window / ⅔-lifetime,
domain_renewal.go) has been failing for weeks — exactly the scenario (ACME outage, quarantined batch-mates deferring renewal into the last 7 days viaquarantineCompactionWindow) in which the synchronous retry is most likely to also fail. The last 24h before expiry is when the proxy should be squeezing every drop out of the cert it holds, not gambling handshakes on a live order.Secondary effect: waiters on the in-flight provisioning single-flight get
getCertForDomain(which ignores expiry) and are fine; only the triggering handshake eats the error. So the failure is intermittent per-connection — worse to diagnose.Expected
Registered domains mirror the dynamic path: while
time.Until(cert.NotAfter) > 0, serve the held certificate and request replacement asynchronously (the renewer and pending-batch machinery already exist). Synchronous handshake provisioning remains only for domains with no usable certificate at all (first issuance) — that is the one case where blocking the handshake buys anything.The
directoryMismatchreprovision from #100 should follow the same rule: serve what we hold, replace in the background.Acceptance criteria
NotAfterFound during the SAN grouping / primary-domain-validity audit (2026-08-13).