No way to serve a name no public CA will issue for #50
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The gap
ER can only serve a name Let's Encrypt will issue for. There is no way to serve HTTPS on anything else:
.localhost,.local,.test,.invalid,.example) — skipped byis_issuable, markedNotCertifiable, handshake refused.The self-signed fallback (
TLS_FALLBACK_CERT) covers only names ER was never configured for. A configured route whose name cannot be certified gets a refused handshake, deliberately — ER must never serve one name's certificate for another (#38).Can a public CA issue for
grafana.localhost?No, and this is settled rather than a limitation of Let's Encrypt specifically. CA/Browser Forum Baseline Requirements forbid issuing for names outside the public DNS, and
.localhostis reserved by RFC 6761 and is not in the root zone — so there is nothing for any challenge type to validate. Every publicly trusted CA is bound by this.Can a private ACME CA?
Yes.
step-ca, Caddy's internal CA and a self-hosted Boulder are ordinary ACME servers that will issue for any name, including reserved ones. They are not publicly trusted, so clients must trust their root — which for local dev or an internal estate is exactly the trade you want, and is strictly better than a self-signed certificate because one root covers every name.ER already speaks ACME, so this is a configuration gap rather than a new subsystem. Two things block it:
acme::directory_urlreturns onlyLetsEncrypt::StagingorLetsEncrypt::Production.[acme]takesemailandstaging, with no way to name a directory.is_issuableskips reserved suffixes unconditionally, before any order is attempted.Two candidate designs — not decided
James wants to think about the how before either is built; this issue records the options and the facts, not a chosen approach.
(a) Configurable ACME directory. Add
directory_urlto[acme], defaulting to Let's Encrypt, plus a trust anchor for ER's own connection to it. Allow reserved names when a non-public directory is configured, since the reason for skipping them is that public CAs cannot issue.(b) An operator-supplied certificate source. A route names a certificate and key file that ER loads and watches.
(a) is the smaller change and keeps one mechanism. (b) is the only answer for a certificate that no ACME server will ever issue. They are not exclusive.
Not blocking v2.0.0
Nothing regresses for a name a public CA can issue for. The one behaviour change is that a
.localhostroute served the self-signed fallback on HTTPS before and now refuses — recorded in the release notes. Local dev should use a real name under a zone ER can get a certificate for, which is what devinfra is moving to (*.azula.oates.ws, IsoHex/devinfra#5).Agreed design (James, 2026-10-23) — still not scheduled
Option (a), the configurable ACME directory, with the staging concept generalised. Settles the third open question from the issue body.
Why staging is not a protocol concept
Verified rather than assumed: ACME (RFC 8555) has no notion of staging. There is only a directory URL. Staging is an environment Let's Encrypt happens to operate, with its own untrusted root and looser rate limits, and clients that offer a
--stagingflag simply ship hardcoded knowledge of that URL — certbot'sSTAGING_URIis the Let's Encrypt staging directory, and--stagingtogether with a non-default--serveris an error ("--server value conflicts with --staging").A private CA such as
step-cahas no staging environment at all, because it is your own CA. So staging cannot be modelled as a protocol-level flag; the directory URL is the primitive, and "staging" is one CA's second server.Shape
directory_urlis required and is what a certificate is normally issued from. Defaulting it to Let's Encrypt production keeps existing configs working.use_test_directoryselectstest_directory_urlinstead. Setting it with notest_directory_urlis a startup error, the same class as omittingdirectory_url— consistent with this codebase erroring on ambiguity rather than ranking silently.dry_run. Certbot's--dry-rundiscards the certificate while--stagingkeeps it; this keeps it, so it behaves like staging. Naming it dry-run would invite the assumption that nothing is persisted, which is how certbot grew #10494.Identical URLs are allowed, with a warning
Initially argued these should be a hard error, on the reasoning that a dry run against production would burn the duplicate-certificate limit. That reasoning was wrong, and the mechanism is worth stating because it is what makes the feature safe:
migration 013 stamps every certificate with the directory that issued it, and
needs_reissuetreats a directory change as needing an order. When the test and live URLs are identical the configured directory does not change, soneeds_reissuefalls through to the ordinary expiry check — false for a healthy certificate. Test mode therefore reissues nothing. Existing certificates keep serving, no rate limit is consumed, and only names that have no certificate yet are ordered.That makes identical URLs a legitimate configuration rather than a footgun:
What is lost is isolation for new names: with both URLs on a public CA, test mode issues real certificates for names that do not have one. So ER should warn when the two resolve to the same directory and the test one is selected, saying plainly that no isolation is in effect and why someone might want that anyway. A warning, not an error — ER cannot tell a rate-limited public CA from a private one, and refusing would block the private case for a problem only the public case has.
Also noted: the switch between directories is already safe
The workflow of "point at the test directory, bring ER up, confirm everything issues, shut down, switch to production, bring it up again" needs no extra mechanism. Because certificates record their issuing directory, changing the URL marks every certificate from the old one as needing reissue, so test certificates are replaced rather than left serving untrusted. That fell out of the staging-flip fix (
fa2e643) rather than being designed for this.Explicitly deferred: several directories with per-name selection
A mix of private ACME for internal names and Let's Encrypt for public ones on one ER is a real end state — Kyoshi has both kinds of name. It is not part of this and is not scheduled.
It is a materially bigger design, because choosing a provider per certificate reintroduces per-name certificate configuration, which v2.0.0 removed in favour of deriving everything from the route table. A route carrying
acme = "internal"is[[tls]]by another name, with the same precedence and conflict questions. If it becomes a requirement it gets its own issue and its own thinking; recorded here so the want is not lost.