TLSStore (no cert-manager)Every public hostname on this cluster is Cloudflare-proxied, so Cloudflare terminates the client-facing
TLS connection and the origin only ever needs to satisfy Cloudflare itself. Traditionally that still
meant ACME certificate management on the origin: cert-manager, an issuer, a Certificate per host, a
renewal loop, and a failure mode that shows up as a certificate expiring because a renewal silently
failed.
There is also a hard constraint on this cluster: it is a single node. An expired certificate cannot be worked around the way a failed deployment can, and there is no second node to serve traffic while the renewal is fixed.
The immediate trigger for this decision was worse than an expired certificate. A private key that was
actively being served had been committed to git on 2026-05-10 and stayed there for four and a half
months, unnoticed, until a gitleaks scan found it — while a per-host Certificate resource was
simultaneously pointing at hand-made secrets. The TLS setup had more moving parts than the situation
needed, which is what made that invisible.
One Cloudflare Origin CA wildcard certificate, served as Traefik’s default certificate. No ACME, no per-app certificates, no cert-manager.
*.upayan.dev and vps.upayan.dev. Validity: 2026-09-28 → 2027-09-28
(one year). This record originally said “until 2041-05-06”, which was the predecessor
certificate’s date — see the correction under Consequences, because that error had an operational
consequence: it made a hand-run annual renewal look like a 15-year non-problem.kube-system/wildcard-upayan-dev-tls, SOPS-encrypted in
k8s/platform/traefik/secrets.sops.yaml (ADR 0002) — the private key is never committed in plaintext.TLSStore default in kube-system (k8s/platform/traefik/tlsstore.yaml) makes it Traefik’s default,
so Ingresses declare no tls: block at all and inherit it.cert-manager was removed entirely on 2026-09-28: its Application, values, controllers, CRDs,
webhooks, and every Certificate/CertificateRequest/Order/Challenge object.TLSStore default serves
every Ingress. The certificate it replaced was a Let’s Encrypt one valid to 2026-11-22 that would
have had to renew, so some renewal process was needed either way; the question was only which one.
This bullet used to claim a 15-year Origin CA certificate “does not need them”, and that turned out
to be wrong (see Consequences) — the reason to prefer the Origin CA is the trust anchor for a
Cloudflare-proxied origin, not the absence of an expiry.Certificate resource per app. Many objects and many renewals, all for hostnames Cloudflare
already terminates.tls:. The referenced per-app secret does not exist, so the
annotation-free form is the correct one; six stale references were removed when this landed.a.b.upayan.dev fails the TLS handshake at Cloudflare’s edge — the origin
is never reached, so the app can be perfectly healthy and still be unreachable. The fix is a
single-level hyphenated name (the convention everywhere else) or an edge certificate. This is a
Cloudflare constraint and cannot be fixed from the origin.CLEAN-002)”. That was written from
the 2041 assumption and is wrong: there is a real date to watch, so CLEAN-002 must replace the
dead cert-manager-based rule with a real expiry metric, not delete it. Deleting it with no
replacement turns a managed expiry into an unannounced cluster-wide outage.docs/certificates.md,
docs/architecture.md, AGENTS.md, .claude/CLAUDE.md and the migration plan) stated
“valid to 2041-05-06” as the current certificate. Measured live, what the origin serves is
notBefore 2026-09-28 → notAfter 2027-09-28, serial 699090E41816073595D26CDDE0568CFE6C66BCB7: the
reissue requested one year, where its predecessor had fifteen. The 2041 date belonged to the
revoked predecessor — and neither the doc nor the review noticed the change, which is exactly the
failure mode this record exists to prevent.
The generalisable lesson: an expiry claim in prose is a claim about one specific certificate, and
every reissue invalidates it. Read the date off the certificate, never off the documentation.k8s/platform/traefik/{tlsstore.yaml,secrets.sops.yaml,traefik-config.yaml}, docs/certificates.md
(including the two-level-hostname limitation), and the S7 entries in changelog/2026-09.md that record
the re-issue, the revocation and the gitleaks finding.