Files
RustProxy/docs/RENEWAL.md
T
2026-10-08 10:36:34 +02:00

7.9 KiB

Automatic certificate renewal

Scope and defaults

RustProxy 0.7.0 renews configured managed certificates whose issuer metadata identifies an existing HTTP-01, Cloudflare, OVH or RFC2136 Certbot profile. Issuance and initial library import are still explicit, staging-first operations. Importing a PEM pair alone does not create a managed lineage or authorize CA requests.

For managed certificates, issuer.auto_renew defaults to true, including saved 0.6 metadata lacking that field. Explicit false opts out. Therefore upgrading an already configured managed certificate can enable scheduled CA checks; review this before starting the new binary. Missing profiles fail closed with an error; there is no silent recreation, provider switch or production issuance.

Certificates shows an independent enable/disable action, Check now and Dry run, last check, next check, expiry and result. Expiry is populated after a worker check; it is not a pre-existing inventory of every manually imported certificate. Renewal is not a configurable new ACME account lifecycle or revocation feature.

Schedule and recovery

Saved renewal observations are loaded from SQLite before administration listeners are exposed, so last check, next check, expiry and result are available immediately after a restart without pressing Check now. RustProxy then waits 5-10 minutes before the first automatic CA check, avoiding a renewal burst during startup. Successful checks are scheduled approximately every 12 hours with up to 30 minutes of additional jitter. Only one issue/renew worker runs at once. Failures back off from one to six hours; a missing/invalid profile is checked again after one hour. Observation rows contain non-secret status/timestamps only and are stored only in config.sqlite3. RustProxy 0.15.0 no longer reads or imports historical JSON observation sidecars at runtime. When SQLite has no observation row, the normal scheduler safely reconstructs status on its next automatic check; this does not replace or regenerate certificate, key, account or renewal-profile material. A manual dry run does not postpone a real due check. The service must be running; this is not an independent timer that works while RustProxy is stopped.

The helper runs certbot renew --cert-name rustproxy in the original isolated lineage, reusing saved authenticator/account/credential settings. It does not use --force-renewal, change the CA or run directory hooks. Do not add shell hooks to the private Certbot renewal configuration: hooks already saved by an operator are still part of Certbot's trusted configuration. Certbot decides when the certificate needs renewal. A successful exit can mean nothing was due: actual certificate and key bytes are compared with the currently installed generation.

For changed material, the helper verifies validity times using OpenSSL and creates private immutable, content-addressed staging files. Rust validates the certificate/ key match and requested domain coverage, then validates a complete new snapshot including current vhost assignments. Only a successful revisioned SQLite write publishes that snapshot. New TCP and QUIC TLS handshakes use the new certificate; existing connections are not restarted. A simultaneous manual replacement/removal or automatic-renewal disable wins over an older automatic worker result.

Worker failure, invalid PEM/SANs, expiry regression or configuration conflict leaves the previously active generation intact. If Certbot succeeded but publication did not, the next check compares files again and can retry the same staged generation. Unchanged checks do not write a new configuration revision. Changed certificate publications use the ordinary revisioned configuration path, but certificate inventory changes are not part of cache compatibility and therefore do not discard otherwise compatible hot/cold objects.

Private profile/file checks, single-worker and per-profile locks, bounded subprocess timeouts and process-group cancellation constrain work. Original certificate files and Certbot archives are retained, not automatically deleted. Immutable renewal staging is limited to 1024 keys; initial issuance staging remains limited to 128. Plan monitored disk space and controlled offline cleanup after verified backups; there is no automatic certificate garbage collector or external expiry alert yet.

Provider-specific prerequisites

HTTP-01 still requires public TCP port 80 to reach an active plaintext RustProxy listener for every name. The reserved challenge handler exposes only bounded tokens for the current worker's domains, before application auth/ACL/cache. It is not a public file browser. Wildcard certificates use DNS-01, not HTTP-01.

Cloudflare, OVH and RFC2136 need their original DNS permissions, credentials and propagation settings. Secrets are stored in private files, not returned by the renew endpoint or included in desired configuration. They are not encrypted at rest. The helper, Certbot, plugins and OpenSSL must be usable by the service user. Keep absolute profile paths stable; moving the Certbot directory requires explicit migration of its stored paths. A system-wide timer should not also manage these RustProxy-isolated profiles.

For a certificate intentionally owned by a separate system Certbot instead of this managed renewal subsystem, 0.28.1 can reference the Certbot lineage directly. Configure a manual certificate id with both absolute paths, for example /etc/letsencrypt/live/example.com/fullchain.pem and /etc/letsencrypt/live/example.com/privkey.pem. RustProxy resolves the live/ symlinks again on every targeted reload, so a renewal wrapper only needs to call:

curl --fail --show-error -X POST \
  -H 'Authorization: Bearer TOKEN' \
  -H 'X-RustProxy-Certificate-Key: certbot-main' \
  -H 'Content-Type: application/json' \
  --data '{}' \
  https://proxy-admin.example/api/certificates/shared-cert/reload

The service identity must be able to traverse the Certbot directories and read the files. External private keys may be owner-only (0600) or group-readable (0640) for a restricted group; they must not be executable, group-writable or world-accessible. The referenced files are not copied into RustProxy backups.

If direct read access to the Certbot tree is undesirable, use the copy/deploy workflow instead:

/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
  --cert /etc/letsencrypt/live/example/fullchain.pem \
  --key /etc/letsencrypt/live/example/privkey.pem

That command resolves the Certbot live/ symlinks locally, validates and stages a managed pair, then publishes the existing id. Do not use deploy for an id configured with external absolute paths, and do not point either external-manual workflow at a certificate carrying RustProxy issuer metadata; one lineage must have one owner.

Staging certificates are not trusted by browsers. A Dry run checks renewal validation but does not deploy the staging result. Only an explicit production issuance creates a production profile; renewal never upgrades staging implicitly.

API

All routes below require the existing local administrator session and CSRF checks.

  • GET /api/acme: current job, provider names, nominal interval and renewal states.
  • POST /api/acme/renew: {"certificate_id":"site","dry_run":false}; returns 202 and a job. Only identity/options, no API credential resubmission.
  • Certificate opt-out is a revisioned config update setting tls.certificates[].issuer.auto_renew to false.

--dry-run is not a claim of live CA validation in this release's test report: packaging tests used a simulated Certbot. Run a real staging/production-lineage validation on your server. Full runtime scheduler/publication acceptance is pending.

Primary references

Certbot renewal behavior and flags: https://eff-certbot.readthedocs.io/en/stable/using.html#renewing-certificates https://eff-certbot.readthedocs.io/en/stable/man/certbot.html

Challenge types: https://letsencrypt.org/docs/challenge-types/