brainjar.net served an expired certificate for two days (2026-10-07..09):
the old brain-jar.com bundle (CN brain-jar.com, SANs incl. all eight
brainjar names) stayed in /etc/haproxy/certs and won the SNI match over the
valid brainjar.net bundle, because superseded-cert cleanup matched on CN
only, and renew-certificates.sh re-published the expired lineage every 12h.
- Retirement now matches on ANY shared name (CN or SAN). The overlapping pem
is moved to cert-retired/ and its lineage's live/ dir and renewal conf to
live-retired/ and renewal-retired/ (after HAProxy loads the new bundle).
Nothing is deleted any more: `certbot delete` is gone from this path.
- Shared-PEM safety: a candidate is kept when it is still the only valid
certificate for a name in the domains table (retirement_blockers()). Expired
certs never block; names the caller lists in the new optional
`unreachable_names` body field do not block.
- /api/ssl/bundle passes --keep-until-expiring and reports `renewed`
(fingerprint before vs after), `certbot_not_due` and the certificate's
`not_after`, so WHP can stop recording certbot no-ops as renewals.
- Never publish an expired certificate: renew-certificates.sh and
sync-certificates.sh skip expired lineages (ERROR-logged, counted
separately), and cert_publish / publish_pem_bundle refuse expired leaves.
- VERSION 2026.10.3.
Tests: test-cert-write-safety.py 31 -> 47, test-cert-scripts.py 38 -> 44,
each new group verified to fail against a reverted fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>