Practical guide · cert-manager
cert-manager: Certificate is Ready, but the Ingress serves the wrong certificate
cert-manager reports Ready=True, yet your browser still receives an old or unexpected certificate. Compare three things: the certificate stored in the Secret, the Secret referenced by the Ingress, and the certificate received on a fresh TLS connection.
This guide covers Kubernetes Ingress with cert-manager and TLS termination at the Ingress controller. Adapt the context, namespace, resource names and hostname before running the read-only diagnostic commands. They assume Bash on Linux, kubectl, OpenSSL and GNU coreutils. Gateway API and TLS passthrough use different configuration paths.
1. Read the Certificate status in the intended cluster
Set these variables in one Bash session and keep that session for the Kubernetes commands below. Use an explicit context so that a different default cluster cannot redirect the checks. Read the conditions, events, validity dates and renewal time in the Certificate description.
TLS_CONTEXT=your-context
TLS_NAMESPACE=apps
TLS_CERTIFICATE=site
TLS_INGRESS=site
kubectl --context "$TLS_CONTEXT" -n "$TLS_NAMESPACE" \
get certificate "$TLS_CERTIFICATE"
kubectl --context "$TLS_CONTEXT" -n "$TLS_NAMESPACE" \
describe certificate "$TLS_CERTIFICATE" Ready=True describes cert-manager’s state for this Certificate; it is not an external TLS test. If Ready is False or missing, follow the reported reason through the referenced Issuer or ClusterIssuer and CertificateRequest. Orders and Challenges are relevant when that issuer uses ACME. Avoid deleting the Secret or repeatedly forcing issuance as a first troubleshooting step.
2. Record the certificate stored in its Secret
Read spec.secretName from the Certificate rather than guessing the Secret name. The following command prints the public leaf certificate’s fingerprint, issuer, dates and SANs. Reading a Secret requires Kubernetes permissions; do not paste a complete Secret export into logs or support tickets.
set -o pipefail
TLS_SECRET=$(kubectl --context "$TLS_CONTEXT" -n "$TLS_NAMESPACE" \
get certificate "$TLS_CERTIFICATE" -o jsonpath='{.spec.secretName}')
if [ -n "$TLS_SECRET" ]; then
kubectl --context "$TLS_CONTEXT" -n "$TLS_NAMESPACE" \
get secret "$TLS_SECRET" -o jsonpath='{.data.tls\.crt}' |
base64 --decode |
openssl x509 -noout -sha256 -fingerprint \
-issuer -dates -ext subjectAltName
fi Keep the SHA-256 fingerprint as the expected value. A certificate covering example.com can still be a different certificate from the one you intended to deploy. Dates and names help diagnose the problem, but the fingerprint identifies the leaf being compared.
3. Check which Secret the Ingress references
Inspect spec.tls[].secretName, spec.tls[].hosts, the routing hosts and ingressClassName. The TLS Secret belongs in the same namespace as the Ingress. Its certificate must cover the hostname. Check that the intended controller handles this Ingress class and can read the Secret.
kubectl --context "$TLS_CONTEXT" -n "$TLS_NAMESPACE" \
get ingress "$TLS_INGRESS" -o yaml
kubectl --context "$TLS_CONTEXT" -n "$TLS_NAMESPACE" \
describe ingress "$TLS_INGRESS" Compare the Ingress reference with the Certificate’s spec.secretName. If they differ unintentionally, fix the manifest, Helm values or GitOps source that owns the Ingress. With an annotated Ingress, cert-manager’s ingress-shim creates Certificate resources from that configuration; inspect its annotations before adding a second Certificate for the same Secret. If the references are correct, inspect the Ingress controller’s events and logs for loading or permission errors.
4. Compare with a fresh TLS connection
Run this probe from outside the hosting network, on an endpoint you operate or are authorized to inspect. Set the hostname again if this is another shell. SNI tells the server which hostname you requested. Compare the printed fingerprint with the Secret’s fingerprint.
set -o pipefail
TLS_HOST=example.com
timeout 10 openssl s_client -connect "$TLS_HOST:443" \
-servername "$TLS_HOST" </dev/null |
openssl x509 -noout -sha256 -fingerprint \
-issuer -dates -ext subjectAltName This displays the presented certificate; it does not establish trust in its chain or validate the hostname. A CDN or load balancer terminating TLS before the Ingress can legitimately serve a different certificate. Check that component, then test the origin separately if reachable. To probe a particular address, replace the -connect destination with IP:443, or [IPv6]:443, while preserving -servername. Repeat for relevant addresses and controller replicas.
5. Reproduce the mismatch in a disposable cluster
The script creates a dedicated kind cluster, installs cert-manager and runs Traefik as its Ingress controller. A SelfSigned Issuer creates site-tls for renewal.test. The Certificate becomes Ready, but the Ingress points to another valid certificate for the same hostname in stale-tls. Changing that reference to site-tls changes the certificate received by the TLS probe.
Download and inspect the cert-manager demonstration
Requirements: a local Docker daemon, kind, kubectl, curl, Bash, OpenSSL and GNU coreutils on Linux. Allow several GiB of memory and disk space for the cluster and downloaded images. Tested with kind 0.33.0, Kubernetes and kubectl 1.35.8, cert-manager 1.21.3 and Traefik 3.7.16.
Read the script, then run bash cert-manager-ingress-check.sh. It uses a private kubeconfig, a loopback port-forward and removes its cluster and temporary files on exit. Downloaded host images remain cached.
READY stays True; BEFORE differs from EXPECTED; AFTER matches EXPECTED. Assertions verify these values. This tests real cert-manager issuance and Ingress certificate loading with self-signed certificates, not ACME validation, renewal or public browser trust.
6. Verify the result, then configure expiration alerts
After correcting the configuration, repeat the external probe and record the address, SNI, observation time and fingerprint. If the expected certificate is served but the browser still rejects it, investigate the chain, hostname coverage and issuer trust. Renewing again does not resolve every TLS error.
Nocert periodically observes certificates served by reachable TLS endpoints and evaluates expiration alert rules in a daily cycle. Check the endpoint and observation time, then configure recipients. This complements the immediate probe after a deployment. Nocert does not update your Kubernetes Secret or Ingress, renew the certificate or guarantee that every visitor reaches the same certificate.