Your TLS Certificates Are About to Expire (and Take Your Site With Them): A cert-manager Guide for SMBs in 2026

Your TLS Certificates Are About to Expire (and Take Your Site With Them): A cert-manager Guide for SMBs in 2026

Somewhere in the next 90 days, an expired TLS certificate is going to take down a small business’s website, API, or email in the middle of the night. It’s not a sophisticated attack. It’s not a cloud outage. It’s a certificate that reached its end date while nobody was watching — and browsers, mobile apps, and payment integrations all refuse to talk to a server they can’t trust.

In 2026, certificates are shorter-lived than ever (Let’s Encrypt pushed the industry to 90-day certificates, and shorter lifespans keep coming). That means the manual “renew it when I remember” approach fails four times more often than it used to — and each failure is an outage. The fix is boring, proven automation: cert-manager on Kubernetes, with monitoring that proves renewal is actually working. Here’s the complete setup for a lean SMB team.

Why certificates still expire in 2026

Renewal failures almost never happen because automation is missing. They happen because automation runs silently and nobody verifies it. Common failure patterns we see in SMB audits:

  • A cron job that renews certificates but logs to a file nobody reads — the cert fails validation in week two and expires in week thirteen.
  • Wildcard certificates generated once, copied to three servers, and renewed on none of them.
  • Let’s Encrypt’s rate limits tripping on a misconfigured testing loop, blocking the real renewal.
  • DNS-01 challenges failing because the DNS provider’s API token expired — and the challenge retry logic silently gives up.

The business impact is outsized: one expired cert can take down your website, your customer portal, and your email authentication (DKIM/SPF don’t help when the TLS handshake itself fails). The good news: on Kubernetes, cert-manager turns issuance and renewal into declarative, observable infrastructure. If your DNS is still managed by hand-edited panels, our DNS-as-code guide is the foundation this build assumes.

Install cert-manager and create a staging ClusterIssuer

Install cert-manager with Helm (you’ll need its CRDs):

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true

Then define a ClusterIssuer so every namespace in the cluster can use the same issuance policy. Always start with Let’s Encrypt’s staging environment — it has much friendlier rate limits for testing:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging-key
    solvers:
      - http01:
          ingress:
            class: nginx
kubectl apply -f clusterissuer-staging.yaml
kubectl get clusterissuer letsencrypt-staging -o wide

A READY=True status means ACME is reachable and the account is registered. Point one test Ingress at it with the annotation cert-manager.io/cluster-issuer: letsencrypt-staging, confirm the certificate issues, and then swap the issuer to letsencrypt-prod. Never skip the staging round — a failed request against production rate limits can block a real renewal for a week.

Issue and renew automatically: HTTP-01 vs DNS-01

For plain web services, cert-manager solves HTTP-01 challenges automatically by injecting a token into your Ingress — zero extra config per host:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["shop.yourdomain.com"]
      secretName: shop-web-tls
  rules:
    - host: shop.yourdomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: shop-web
                port:
                  number: 80

For wildcards or services that must stay reachable during issuance (APIs hit by third parties), use a Certificate resource with the DNS-01 solver instead. Here’s a Cloudflare wildcard example — the API token goes in a Secret, which you should source from your secrets manager rather than committing it (see our secrets management guide):

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-yourdomain
  namespace: platform
spec:
  secretName: wildcard-yourdomain-tls
  dnsNames:
    - "*.yourdomain.com"
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  privateKey:
    algorithm: ECDSA
    size: 384

cert-manager tracks the certificate lifetime and renews automatically at two-thirds of the validity period — for a 90-day certificate, that’s 30 days of slack. It also retries with exponential backoff, so transient ACME failures don’t become expiration events. An ECDSA key (instead of RSA) keeps handshakes fast for mobile clients.

Prove renewal works — monitoring that catches failures first

If you can’t prove a renewal happened, assume it didn’t. Three cheap checks give you 95% of the safety:

1. Expose cert-manager’s metrics. The Prometheus metrics endpoint includes certmanager_certificate_expiration_timestamp_seconds per certificate. Alert when any cert has less than 21 days left or when a Certificate object reports Ready=False:

groups:
  - name: certs
    rules:
      - alert: CertificateExpiringSoon
        expr: certmanager_certificate_expiration_timestamp_seconds - time() < 21 * 86400
        for: 30m
        labels:
          severity: warning

2. Check from the outside, where users sit. A cron job that hits each public endpoint and fails if the served cert expires soon:

#!/usr/bin/env bash
# check-tls.sh — alert if any host's cert expires within 21 days
for host in www.yourdomain.com api.yourdomain.com mail.yourdomain.com; do
  if openssl s_client -servername "$host" -connect "$host":443 \
      </dev/null 2>/dev/null | openssl x509 -noout -checkend 1814400; then
    echo "OK: $host safe for 21+ days"
  else
    echo "ALERT: $host expires within 21 days"; exit 1
  fi
done

Wire the exit code into your monitoring (Uptime Kuma, Grafana, or even a cron notification). checkend 1814400 is 21 days in seconds — fail, loudly, with three weeks of runway, not three days.

3. Add a renewal dry-run to your pipeline. The certbot renew --dry-run equivalent in cert-manager land is simply recreating the Certificate in a test namespace against the staging issuer during CI — the same discipline as testing any other deployment change (and the exact lesson from our safe deployment pipeline guide).

When renewal fails: a practical troubleshooting path

The three failures that actually happen, and how to confirm them in minutes:

  • Rate limited by Let’s Encrypt: kubectl describe certificate shows the ACME error; acme-v02.api.letsencrypt.org returns 429. Fix: use the staging issuer for tests, and wait out the window (failures reset after a week).
  • DNS-01 can’t find the TXT record: the error “no matching TXT record” almost always means propagation delay or a typo in the zone. Check with dig TXT _acme-challenge.yourdomain.com and confirm your DNS provider zone actually matches the domain in the Certificate.
  • HTTP-01 blocked by a firewall: the challenge requires port 80 to be reachable from the internet. If your cluster sits behind a whitelist, use DNS-01 instead — it needs no inbound traffic at all.

Automated certificate management is one of the highest-ROI reliability investments an SMB can make: a few YAML files eliminate a whole class of production incidents permanently. But it’s only trustworthy if the renewal loop is verified end-to-end — which is exactly what the checks above give you.

Not sure your current certificate setup will survive the next 90 days? Book a free 30-minute DevOps consulting session — we’ll audit your TLS, DNS, and deployment pipelines and hand you a prioritized fix list.

en_GBEnglish
Scroll to Top