Skip to content

fix: Auto-renew certificates by default, with honest renew reporting and proxy tuning#167

Merged
nfebe merged 5 commits into
mainfrom
enh/cert-renew-and-serving-perf
Jul 7, 2026
Merged

fix: Auto-renew certificates by default, with honest renew reporting and proxy tuning#167
nfebe merged 5 commits into
mainfrom
enh/cert-renew-and-serving-perf

Conversation

@nfebe

@nfebe nfebe commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Three changes on one branch, from the Albacore serving and reliability pass.

Certificates no longer silently expire. Two compounding problems in the renewal path: automatic renewal only started when auto_renewal_enabled was explicitly set (and the shipped example set it off), so the renewer never ran; and even when it did, the global flag gated the whole renewer, so a certificate the user had individually marked for auto-renew could still be vetoed by the global setting. Now the renewer runs whenever certbot is enabled, auto-renewal defaults on, and each certificate's own auto-renew setting is authoritative. The global flag only supplies the default for certificates that were never toggled; an explicit per-certificate choice always wins.

Single-certificate renewal is honest, with force as an explicit option. The per-certificate Renew action reported success even when it renewed nothing (certbot skips a not-yet-due certificate and exits 0). It now reports whether a certificate was actually reissued, and force-renewal is an explicit request option (?force=true) the UI can offer deliberately, rather than being applied to every renew.

Proxy defaults tuned for faster deployment serving. Upstream requests now use HTTP/1.1 on every proxied route rather than only the primary one, access logging is buffered, gzip covers modern asset types (JavaScript, wasm, web fonts) with a minimum-size floor, and proxy read buffers are sized up. Validated with openresty -t against the real openresty:alpine image.

Deferred and tracked separately: reusing connections to app containers (Albacore #158) and routing externally-fronted proxy domains (#166).

nfebe added 2 commits July 6, 2026 19:23
The per-certificate Renew action reported success but did nothing when the
certificate was not yet inside certbot's auto-renew window: it asked certbot to
renew only if due, so a not-yet-expiring cert was skipped and the action was a
silent no-op. A single renew is an explicit "renew this now" request, so it now
forces the reissue. Renew-all is unchanged and still renews only what is due.
Deployed apps are served to end users through the generated reverse proxy, and
its defaults left several easy wins unused. Upstream requests now use HTTP/1.1
on every proxied route rather than only the primary one, access logging is
buffered instead of writing per request, gzip covers modern asset types
(JavaScript, wasm, web fonts) with a minimum-size floor, and proxy read buffers
are sized up.

The larger win, reusing connections to app containers, is deferred: it requires
reworking the runtime DNS-resolution the proxy relies on to keep serving other
sites when one backend is briefly down. Tracked in the Albacore serving item.
@sourceant

sourceant Bot commented Jul 6, 2026

Copy link
Copy Markdown

Code Review Summary

This PR improves SSL reliability and proxy performance. It fixes a significant issue where certificates would expire because auto-renewal was off by default, introduces 'honest' reporting for renewals, and tunes Nginx for higher throughput.

🚀 Key Improvements

  • Auto-renewal now defaults to enabled using *bool configuration to detect unset states.
  • Nginx proxy tuning (HTTP/1.1, buffering, expanded gzip types).
  • Per-certificate renewal overrides via file markers.
  • Added support for ?force=true in the renewal API.

💡 Minor Suggestions

  • Simplify certbot output parsing logic.
  • Verify if Nginx log buffering impacts real-time monitoring requirements.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

Forcing a reissue on every single-certificate Renew was wrong for a not-yet-due
certificate and risks hitting duplicate-certificate limits. Force is now an
explicit request option the UI can offer deliberately. The default renew asks
certbot to renew only if due and reports honestly whether a certificate was
actually reissued, rather than always claiming success. Automatic renewal uses
the same non-forced path.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/ssl/manager.go

// certbotDidRenew reports whether a `certbot renew` run actually reissued a cert,
// by looking for the phrases certbot prints when it skips a not-yet-due lineage.
func certbotDidRenew(output string) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relying on string matching for certbot output is fragile across different versions or locales. While effective for a quick fix, a more robust check would involve inspecting the certificate's expiration timestamp on disk using crypto/x509 after the certbot command completes to verify if the file changed.

Suggested change
func certbotDidRenew(output string) bool {
func certbotDidRenew(output string) bool {
lower := strings.ToLower(output)
return !strings.Contains(lower, "not yet due") &&
!strings.Contains(lower, "no renewals were attempted")
}

Certificates could expire even with the renewal checker running, because
automatic renewal only started when explicitly enabled in config and the shipped
example set it off. Auto-renewal now defaults on, so an install with certbot
enabled renews its certificates before they lapse without extra configuration.
An explicit false is still honored for anyone who wants to opt out.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@nfebe nfebe changed the title enh: Tune proxy serving defaults and fix single-certificate renewal fix: Auto-renew certificates by default, with honest renew reporting and proxy tuning Jul 6, 2026
The global auto-renewal flag gated the entire renewer, so a certificate the user
had individually marked for auto-renew would still lapse whenever the global
setting was off. The renewer now runs whenever certbot is enabled, and each
certificate's own auto-renew setting decides whether it renews. The global flag
only supplies the default for certificates that were never toggled; an explicit
per-certificate choice, on or off, always wins over it.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/api/server.go

func (s *Server) renewCertificate(c *gin.Context) {
domain := c.Param("domain")
force := c.Query("force") == "true"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While query parameters are typically strings, explicitly checking for 'true' is fine here. However, consider if this should also support '1' or 'yes' if the UI or external callers might provide them. For now, strict 'true' is acceptable but worth noting.

Suggested change
force := c.Query("force") == "true"
force := c.DefaultQuery("force", "false") == "true"

Comment thread internal/ssl/manager.go

// certbotDidRenew reports whether a `certbot renew` run actually reissued a cert,
// by looking for the phrases certbot prints when it skips a not-yet-due lineage.
func certbotDidRenew(output string) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string matching logic is slightly redundant. Since multiple phrases result in false, we can simplify this with a loop or a more concise switch. Also, ensure the function handles whitespace-only output correctly.

Suggested change
func certbotDidRenew(output string) bool {
func certbotDidRenew(output string) bool {
lower := strings.ToLower(output)
keywords := []string{
"no renewals were attempted",
"not yet due for renewal",
"no certificates are due for renewal",
}
for _, k := range keywords {
if strings.Contains(lower, k) {
return false
}
}
return strings.TrimSpace(output) != ""
}

@nfebe nfebe merged commit 6b9d744 into main Jul 7, 2026
5 checks passed
@nfebe nfebe deleted the enh/cert-renew-and-serving-perf branch July 7, 2026 00:25
@nfebe nfebe mentioned this pull request Jul 7, 2026
38 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant