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_renewtofalse.
--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/