Skip to content

feat: per-container private key renewal#1268

Merged
buchdag merged 2 commits into
nginx-proxy:mainfrom
JamBalaya56562:feat/renew-private-keys-per-container
Jun 19, 2026
Merged

feat: per-container private key renewal#1268
buchdag merged 2 commits into
nginx-proxy:mainfrom
JamBalaya56562:feat/renew-private-keys-per-container

Conversation

@JamBalaya56562

Copy link
Copy Markdown
Contributor

What

Allow RENEW_PRIVATE_KEYS to be overridden per proxied container via a new LETSENCRYPT_RENEW_PRIVATE_KEYS variable, instead of only globally on the companion.

Why

RENEW_PRIVATE_KEYS could only be set globally, so it was all-or-nothing. The reporter (#1191) wants to keep a stable private key for one certificate — a DANE/TLSA mail cert (rspamd), where rotating the key would break published TLSA records — while still rotating keys on every renewal for their other web certificates. There was no way to do that short of running a second companion instance.

How

Follows the existing per-container nameref pattern (same as ACME_${cid}_RENEW_AFTER, LETSENCRYPT_${cid}_KEYSIZE, etc.):

  • app/letsencrypt_serviceupdate_cert now reads LETSENCRYPT_${cid}_RENEW_PRIVATE_KEYS and falls back to the global RENEW_PRIVATE_KEYS when unset, before deciding whether to pass acme.sh --always-force-new-domain-key.
  • app/letsencrypt_service_data.tmpl — surfaces LETSENCRYPT_RENEW_PRIVATE_KEYS from the proxied container into the per-container data (both the SAN and single-domain-cert branches).
  • docs/Container-configuration.md — documents the per-container override.
  • test/tests/renew_private_keys/ — integration test: a container with LETSENCRYPT_RENEW_PRIVATE_KEYS=false must keep the same private key across a forced renewal, even though the companion's global default (true) would otherwise rotate it. Registered in test/config.sh and the CI matrix.

The global RENEW_PRIVATE_KEYS (and the legacy REUSE_PRIVATE_KEYS) behaviour is unchanged when the per-container variable is not set.

Testing

  • New renew_private_keys integration test added (CI, 2containers).
  • Verified locally that the template renders the new variable correctly (docker-gen emits LETSENCRYPT_<cid>_RENEW_PRIVATE_KEYS="false" for a container setting LETSENCRYPT_RENEW_PRIVATE_KEYS=false).
  • shellcheck clean on the modified script and the new test.

Closes #1191

🤖 Generated with Claude Code

@buchdag buchdag added the type/feat PR for a new feature label Jun 18, 2026
@buchdag buchdag changed the title feat: per-container RENEW_PRIVATE_KEYS via LETSENCRYPT_RENEW_PRIVATE_KEYS (closes #1191) feat: per-container RENEW_PRIVATE_KEYS via LETSENCRYPT_RENEW_PRIVATE_KEYS Jun 18, 2026
@buchdag buchdag requested a review from Copilot June 18, 2026 09:20
@JamBalaya56562 JamBalaya56562 force-pushed the feat/renew-private-keys-per-container branch from 564ddb4 to 82e127a Compare June 18, 2026 09:21

Copilot AI 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.

Pull request overview

Adds a per-proxied-container override for private key rotation on renewal, enabling stable keys for specific certificates (e.g., DANE/TLSA mail certs) while keeping the global default behavior for everything else.

Changes:

  • Add LETSENCRYPT_RENEW_PRIVATE_KEYS support via docker-gen templating into per-container variables.
  • Update certificate issuance logic to honor LETSENCRYPT_${cid}_RENEW_PRIVATE_KEYS (with fallback to global RENEW_PRIVATE_KEYS).
  • Add an integration test and register it in CI and the test suite.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/letsencrypt_service Reads per-container RENEW_PRIVATE_KEYS override and conditionally passes --always-force-new-domain-key.
app/letsencrypt_service_data.tmpl Exposes LETSENCRYPT_RENEW_PRIVATE_KEYS from proxied containers into generated per-container env.
docs/Container-configuration.md Documents the per-container override behavior.
test/tests/renew_private_keys/run.sh New integration test to verify key reuse across a forced renewal when override is false.
test/config.sh Registers the new test in the global test list.
.github/workflows/test.yml Adds the new test to the CI matrix (2containers).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/tests/renew_private_keys/run.sh
Comment thread test/tests/renew_private_keys/run.sh Outdated
Comment thread docs/Container-configuration.md Outdated
JamBalaya56562 and others added 2 commits June 18, 2026 13:32
RENEW_PRIVATE_KEYS could only be set globally, so users could not keep a
stable private key for one certificate (e.g. a DANE/TLSA mail cert) while
still rotating keys elsewhere (nginx-proxy#1191). Add a per-container override using
the existing per-container nameref pattern.

- app/letsencrypt_service: read LETSENCRYPT_${cid}_RENEW_PRIVATE_KEYS,
  falling back to the global RENEW_PRIVATE_KEYS, when deciding whether to
  pass acme.sh --always-force-new-domain-key.
- app/letsencrypt_service_data.tmpl: surface LETSENCRYPT_RENEW_PRIVATE_KEYS
  from the proxied container into the per-container data.
- docs/Container-configuration.md: document the per-container override.
- test/tests/renew_private_keys: integration test asserting a container with
  LETSENCRYPT_RENEW_PRIVATE_KEYS=false keeps its key across a renewal.

Closes nginx-proxy#1191

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rename the per-container override to ACME_RENEW_PRIVATE_KEYS (was
  LETSENCRYPT_RENEW_PRIVATE_KEYS) per maintainer feedback: the ACME_ prefix
  is preferred now that Let's Encrypt is no longer the only ACME CA.
- renew_private_keys test: capture the openssl output and fail explicitly
  when the key can't be read, so an empty fingerprint cannot cause a false
  pass (Copilot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@buchdag buchdag force-pushed the feat/renew-private-keys-per-container branch from 0c929d5 to 2dc1e7a Compare June 18, 2026 11:32
@buchdag buchdag changed the title feat: per-container RENEW_PRIVATE_KEYS via LETSENCRYPT_RENEW_PRIVATE_KEYS feat: per-container private key renewal Jun 19, 2026
@buchdag buchdag merged commit dde6494 into nginx-proxy:main Jun 19, 2026
46 checks passed
@JamBalaya56562 JamBalaya56562 deleted the feat/renew-private-keys-per-container branch June 19, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feat PR for a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Why is RENEW_PRIVATE_KEYS Globally Set Only?

3 participants