diff --git a/internal/pkg/cert/letsencrypt/renewer.go b/internal/pkg/cert/letsencrypt/renewer.go index 210a460..bc53604 100755 --- a/internal/pkg/cert/letsencrypt/renewer.go +++ b/internal/pkg/cert/letsencrypt/renewer.go @@ -46,7 +46,18 @@ // 5. Renew the certificate newCertResources, err := client.Certificate.Renew(certResource, false, false, "") if err != nil { - return nil, fmt.Errorf("failed to renew certificate: %w", err) + // Fallback: If renewal fails (e.g., certificate not found on ACME server, 404), try obtaining a fresh certificate. + // This handles cases where the certificate is expired beyond the renewal window or revoked/missing on the CA side. + fmt.Printf("Warning: Failed to renew certificate for domain %s: %v. Attempting fresh issuance...\n", oldCert.Domain, err) + + request := certificate.ObtainRequest{ + Domains: []string{oldCert.Domain}, + Bundle: true, + } + newCertResources, err = client.Certificate.Obtain(request) + if err != nil { + return nil, fmt.Errorf("failed to renew certificate and failed fallback issuance: %w", err) + } } // 6. Map the results @@ -55,12 +66,12 @@ CertPEM: newCertResources.Certificate, // The renewed key (newCertResources.PrivateKey) is the domain's NEW private key. KeyPEM: newCertResources.PrivateKey, - FullChain: newCertResources.Certificate, + FullChain: append(newCertResources.Certificate, newCertResources.IssuerCertificate...), // Ensure full chain is stored // The ACME account key and URL remain the same. AccountKey: oldCert.AccountKey, AccountURL: oldCert.AccountURL, // The rotation strategy remains the same. - EnableRotation: oldCert.EnableRotation, - RenewBefore: oldCert.RenewBefore, + EnableRotation: oldCert.EnableRotation, + RenewBefore: oldCert.RenewBefore, }, nil }