caddyserver / caddyserver/certmagic
Expired wildcard cert keeps being served for newly-added specific subdomains
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.6k
- Forks
- 354
- Avg merge
- 9d 23h
- Merged PRs (30d)
- 3
Description
What version of the package are you using?
v0.25.3
What are you trying to do?
Run two *certmagic.Config instances sharing one *certmagic.Cache:
- Config A issues and serves wildcard certs (
*.example.com) via DNS-01. - Config B issues and serves only exact-SAN certs via on-demand HTTP-01.
The TLS server picks A or B per handshake based on whether the SNI is currently administratively configured as a wildcard or not. The shared cache is required so a single CacheOptions.GetConfigForCert can route renewals to the right Config.
What steps did you take?
- Through Config A, obtain
*.example.com. It is cached and written to storage. - Wildcard configuration is removed administratively. The cert remains in the cache (and in storage).
- A new specific hostname
test.example.comis added; it now routes to Config B. - A TLS handshake arrives for
test.example.com. The server dispatches it to Config B'sGetCertificate.
Time passes, the wildcard domain is removed, and the DNS challenge is removed. The wildcard cert expires.
What did you expect to happen, and what actually happened instead?
Expected: Config B refuses the cached wildcard cert (different issuance policy, expired) and falls through to obtainOnDemandCertificate to issue a fresh cert for test.example.com.
Actual: getCertificateFromCache's wildcard-name walk in handshake.go:142-152 finds *.example.com in the shared cacheIndex and returns it as matched = true to Config B. The follow-up renewal in optionalMaintenance runs against the SNI (test.example.com), which has no resource in storage; it errors. Because the cert is expired, handshake.go:463-465 still returns it, and it is served on every subsequent handshake. On-demand issuance for test.example.com is never attempted.
Logs repeatedly show:
on_demand renewing certificate on-demand failed
subjects=["*.example.com"] error="context canceled"
How do you think this should be fixed?
A combination of:
-
Per-
Configopt-out of the wildcard cache walk. AddConfig.DisableWildcardCacheFallback bool. When set, skip the label-replacement loop athandshake.go:142-152. Configs that should only serve exact-SAN matches enable it. Minimal change, fixes this report. -
Optional
ScopedCertificateSelectorinterface. ACertificateSelectorthat also implementsScopedToIndexedNames() bool(returning true) tellsselectCertit only needs index hits for the looked-up name. Two effects:selectCertskips thegetAllCerts()fallback athandshake.go:195-197, so deployments with thousands of cached certs avoid a per-handshake[]Certificateallocation proportional to cache size.- Combined with (1) the selector is reached only for true exact-name hits, allowing it to reject wildcard SANs trivially.
It is a non-breaking addition, existing
CertificateSelectorimplementations keep current behavior.
(1) alone resolves the user-visible bug; (2) additionally fixes the allocation hot path
Please link to any related issues, pull requests, and/or discussion
None found in a quick search
Bonus: What do you use CertMagic for, and do you find it useful?
For our SaaS redirect.pizza 🍕
Bug report written by Claude Opus 4.7.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in handshake.go at the wildcard-name walk around lines 142-152, then trace getCertificateFromCache, selectCert, and optionalMaintenance. Confirm how a shared cache lets Config B select an expired wildcard certificate, and make the exact-SAN path reject that fallback so test.example.com reaches on-demand issuance; preserve existing behavior for configurations that use wildcard matches.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100