1. The Symptom — cert-manager's Certificate Stuck at "Ready: False"
When you hit a k3s cert-manager certificate not renewing situation, most teams first suspect DNS configuration or the cert-manager version. But the real culprit usually sits much closer to the metal.

It's not uncommon to see kubectl get certificate stuck at Ready: False indefinitely in a setup that auto-renews Let's Encrypt certificates via cert-manager. A cert-manager GitHub issue documents exactly this: on K3s with Traefik, the HTTP-01 challenge pod stays "Progressing" forever, and accessing it returns a 503 Service Unavailable. The logs show a propagation check failed error, and the resource dependency chain — Certificate → CertificateRequest → Order → Challenge — is stuck somewhere along the way.
Diagnosing this starts with walking through all four resources with kubectl describe. But teams often re-check their Traefik configuration over and over and find nothing wrong. That's because, in most cases, the problem is neither Traefik nor cert-manager itself — it's ServiceLB, a component K3s ships by default. This kind of K3s-specific pitfall — "I configured it correctly, but something else was quietly occupying the resource" — is much faster to spot when you already have visibility into your cluster's day-to-day state.
2. Root Cause 1 — ServiceLB Occupies Ports 80/443 and Blocks the Challenge Pod

K3s ships a lightweight load balancer implementation called ServiceLB (formerly known as Klipper LB) that lets you use LoadBalancer-type Services out of the box. Per the official K3s documentation, ServiceLB creates a DaemonSet for every LoadBalancer-type Service and deploys a pod on each cluster node that directly occupies that service's port via hostPort.
In the default configuration, K3s's bundled Traefik claims ports 80 and 443 as a LoadBalancer service through this same mechanism. As a result, any other LoadBalancer service or hostPort pod requesting the same port number on the same node ends up conflicting and stuck in Pending.
As the k3s-io/k3s discussion explains, ServiceLB pods themselves don't actually process traffic. They set up iptables rules on each node that map external IPs to the ClusterIP and forward traffic from node ports — all handled at the kernel level. In other words, "what you see in the config" and "what's actually forwarding packets" don't match, which is exactly why port-80 reachability issues are so hard to spot just by checking pod logs or Traefik's configuration.
cert-manager's HTTP-01 challenge, according to the cert-manager official documentation, routes to a temporary acmesolver pod using your existing Ingress or Gateway configuration. If port-80 reachability is broken anywhere along that path, the challenge keeps failing silently. Let's Encrypt's HTTP-01 challenge spec mandates that validation always happens on port 80 — the ACME standard doesn't allow specifying an arbitrary port. In other words, as long as port 80 reachability is broken, there's no alternate-port workaround.
3. Root Cause 2 — Traefik Ignores the Challenge Ingress Due to an ingressClassName Mismatch

Another commonly overlooked factor is the ingressClassName on the ACME challenge Ingress that cert-manager auto-generates. As the official Kubernetes documentation explains, an Ingress resource's spec.ingressClassName field determines which Ingress controller processes that resource, and in an environment without a default IngressClass defined, the behavior depends on the controller's implementation.
A k3s-io/k3s GitHub issue reports that when cert-manager's auto-generated challenge Ingress carries ingressClassName: traefik, K3s's bundled Traefik sometimes fails to recognize it. The workaround is to manually remove ingressClassName from the Ingress resource, but that's only a stopgap — the same problem resurfaces the moment the Ingress is regenerated on the next renewal cycle.
The Traefik Labs community forum also documents cases where Traefik fails to respond correctly to requests under /.well-known/acme-challenge/ and instead forwards them straight to the IngressRoute's backend service, as well as cases where an active HTTP-to-HTTPS redirect swallows the challenge request itself and causes it to fail. Root cause 1 (ServiceLB's port occupation) and root cause 2 (the ingressClassName mismatch) are independent problems, but in real-world incidents they often occur together — fixing only one of them may not be enough. Some managed Kubernetes offerings template this Ingress-controller/load-balancer alignment at provisioning time, and Kubo is one of them.
4. The Fix — Pinning Nodes, Adjusting ingressClassName, and Deciding When to Disable ServiceLB

As a quick fix, you can delete and recreate the stuck Certificate, CertificateRequest, Order, and Challenge resources while reviewing the ingressClassName setting — this restores service temporarily, but it's a symptomatic treatment that risks recurring on the next renewal cycle.
There are two directions for a permanent fix. One, per the official K3s documentation, is to use the enablelb and lbpool node labels to pin ServiceLB to a specific set of nodes, reducing the chance of port conflicts. The other is to disable ServiceLB entirely with --disable=servicelb and switch to a full-fledged load balancer implementation designed for bare-metal/on-prem environments, such as MetalLB. MetalLB integrates with your existing network equipment via BGP or L2 mode, delivering cloud-equivalent load balancing on-premises.
Which approach to choose depends on cluster size and operational maturity. For small clusters with few nodes and no complex networking requirements, node pinning is often sufficient. For production clusters that continuously run multiple LoadBalancer services, adopting MetalLB to eliminate the port-conflict risk at the root tends to be the more stable long-term choice.
This kind of "it was working by default, but a specific component was quietly monopolizing a resource" configuration mismatch is a pattern you'll run into repeatedly when operating K3s in production. Kubo's Captain UI gives visibility into this kind of cluster configuration state, making it easier to catch "invisible occupiers" like ServiceLB's port claims or IngressClass mismatches before they cause an incident.
5. Takeaway — K3s's "Silently Skipped Defaults" Can Become the Spark for Production Incidents
K3s ships ServiceLB and Traefik by default as a lightweight Kubernetes distribution, which dramatically cuts down initial setup effort. But when those "silently skipped defaults" go unexamined and make it into production, they can lead to exactly the kind of hard-to-diagnose incident described here — a cert-manager certificate renewal failure.
If you run into a certificate that won't renew, don't stop at checking cert-manager itself or your DNS settings — also check the K3s-specific layer: ServiceLB's port occupation and IngressClass alignment.
If you'd rather reduce these kinds of operational pitfalls up front instead of wrestling with the high cost of EKS or AKS, K3s-based Kubo is worth a look. It comes with Let's Encrypt certificate management built in, and Captain UI's visibility into configuration gaps can help you avoid incidents like the one described in this article. If you'd rather not step into the same trap, feel free to reach out via our contact page.