Kubernetes handles SSL certificates differently from traditional web servers. Instead of cert files on disk, TLS credentials live inside cluster secrets. cert-manager automates renewals. Ingress controllers handle TLS termination. The whole system looks automated — until a renewal silently fails and your service goes down with an expired certificate that nobody noticed.
This guide covers how Kubernetes TLS certificate monitoring actually works: inspecting secrets, watching cert-manager resources, checking ingress TLS status, and getting external visibility on what your cluster is actually serving.
In Kubernetes, TLS certificates are stored as Secrets of type kubernetes.io/tls. These contain two fields: tls.crt (the certificate chain) and tls.key (the private key).
kubectl get secrets --all-namespaces -o json \
| jq -r '.items[] | select(.type=="kubernetes.io/tls") | "\(.metadata.namespace)/\(.metadata.name)"'
To inspect the expiry date of a specific TLS secret:
kubectl get secret my-tls-secret -n my-namespace \
-o jsonpath='{.data.tls\.crt}' \
| base64 -d \
| openssl x509 -noout -enddate -subject
To check days remaining for all TLS secrets in a namespace:
#!/bin/bash
NAMESPACE=${1:-default}
kubectl get secrets -n "$NAMESPACE" --field-selector type=kubernetes.io/tls \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' \
| while read secret; do
expiry=$(kubectl get secret "$secret" -n "$NAMESPACE" \
-o jsonpath='{.data.tls\.crt}' \
| base64 -d \
| openssl x509 -noout -enddate 2>/dev/null \
| cut -d= -f2)
days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))
echo "$days days $NAMESPACE/$secret ($expiry)"
done | sort -n
Run this script across all namespaces to get a sorted list of certificates by days remaining — soonest expiry first.
If you're using cert-manager, certificates are represented by Certificate custom resources. cert-manager watches these, issues certificates via an ACME or CA issuer, and stores the result in a TLS Secret. You can query cert-manager's view of certificate status:
# List all Certificate resources and their ready state
kubectl get certificates --all-namespaces
# Describe a specific certificate for renewal status and events
kubectl describe certificate my-cert -n my-namespace
The describe output shows the renewal time, last issued time, and any recent events — including renewal failures. Look for:
Status:
Conditions:
Type: Ready
Status: True
Reason: Ready
Not After: 2026-12-14T00:00:00Z
Not Before: 2026-09-14T00:00:00Z
Renewal Time: 2026-11-24T00:00:00Z
If Ready is False, cert-manager is actively failing to renew the certificate. The events section will show why.
Ready: True while still approaching expiry if cert-manager hasn't attempted renewal yet (renewal begins 30 days before expiry by default). Always check Not After directly, not just the Ready condition.
cert-manager can fail to renew certificates without generating obvious alerts. The most common silent failure scenarios:
HTTP-01 challenges require cert-manager to serve a validation token on port 80. If your ingress is misconfigured, a NetworkPolicy blocks the ACME server, or port 80 traffic is redirected before the challenge path is served, the challenge silently times out:
kubectl describe order my-cert-order -n my-namespace
# Look for: "Error: failed to perform self check GET request"
DNS-01 challenges modify DNS records via a cloud provider API. If the API credentials in your cert-manager secret expire, the DNS provider's API rate-limits the requests, or IAM permissions change, renewals fail silently:
kubectl logs -n cert-manager deployment/cert-manager | grep -i "error\|failed\|challenge"
A ClusterIssuer that loses connectivity to the ACME endpoint (Let's Encrypt rate limit, network policy change, ACME directory URL change) will show Ready: False on the Issuer resource but won't prevent existing certificates from appearing valid until they expire:
kubectl get clusterissuer -o wide
# Check the Ready column — should be True for all issuers
kubectl get pods -n cert-manager
kubectl logs -n cert-manager deployment/cert-manager --since=1h | tail -50
A crash-looping cert-manager controller means no renewals are happening. Existing certificates continue serving, masking the problem until they expire.
Ingress resources reference TLS secrets by name. If the referenced secret expires, the ingress controller continues serving the expired cert until the secret is replaced — no error, no restart, just an expired TLS handshake for every visitor:
# List all ingresses and their TLS configuration
kubectl get ingress --all-namespaces -o json \
| jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name) \(.spec.tls[].secretName // "no-tls")"'
Cross-reference ingress TLS secret names against the expiry script above to prioritize which secrets matter most. An expiring TLS secret that backs a production ingress is critical. One that backs a staging environment is lower priority.
*.example.com), a single expiring secret can take down all subdomains simultaneously. Wildcard certs warrant extra monitoring attention.
Checking TLS secrets with kubectl tells you what's stored in the cluster. It doesn't tell you what the external world sees. The certificate that a visitor's browser negotiates depends on:
An external TLS handshake check — connecting to the actual HTTPS endpoint — is the only reliable way to confirm what a visitor's browser sees:
echo | openssl s_client -connect your-k8s-service.example.com:443 \
-servername your-k8s-service.example.com 2>/dev/null \
| openssl x509 -noout -enddate -issuer -subject
For Kubernetes services, this should be run against the external hostname (via LoadBalancer or Ingress), not the in-cluster service IP.
For production clusters, a monitoring approach that covers all of these failure modes needs to:
External SSL monitoring services check your HTTPS endpoints from outside the cluster on a regular schedule. They see exactly what your users see and alert you when expiry is approaching — regardless of whether cert-manager is healthy.
Use the free SSLWatch checker to instantly verify what any public HTTPS endpoint is actually serving — useful for checking your ingress hostnames from outside the cluster:
Enter any domain and see days remaining, expiry date, and issuer in seconds. No kubectl access needed — checks the live TLS handshake your visitors see.
Check SSL expiry → Get expiry alerts →SSLWatch monitors SSL certificate expiry for any HTTPS endpoint — including your Kubernetes ingress hostnames — and sends email alerts at 30, 14, 7, and 1 day before expiry. It checks via live TLS handshake from outside your cluster, so it catches cert-manager failures, ingress controller caching issues, and CDN cert problems that kubectl checks miss.
No agent to install in your cluster. No dependency on cert-manager's self-reporting. Add your ingress hostnames and get alerts when they actually matter.
SSLWatch monitors all your ingress hostnames with external TLS checks and automatic alerts — independent of cert-manager's health.
Join the SSLWatch waitlist → Free expiry checker →