Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Kubernetes Ingress and TLS with cert-manager

By Kokil Thapa | Last reviewed: September 2026

Exposing a Laravel API or WordPress site on Kubernetes without TLS leaves traffic readable and breaks browser trust. Kubernetes Ingress and TLS with cert-manager automates HTTPS by pairing an Ingress controller with Let's Encrypt certificates that renew before they expire. In a typical Kubernetes cluster architecture, the Ingress sits at the edge while cert-manager watches Certificate resources and talks to ACME CAs. On clusters I have run alongside traditional Apache and Nginx VMs, this pattern replaces manual Certbot cron jobs with declarative YAML you can version in Git.

What is Kubernetes Ingress and TLS with cert-manager?

Ingress is a Kubernetes API object that routes HTTP and HTTPS from outside the cluster to Services inside it. TLS termination happens at the Ingress controller, which reads a Secret containing the certificate and private key. cert-manager is a controller that creates and renews those Secrets using ACME protocols from CAs like Let's Encrypt.

The split of responsibilities matters in practice. Ingress defines hostnames, paths, and which Secret to mount. cert-manager creates the Certificate CR, orders the cert, completes ACME challenges, and writes the tls.crt and tls.key into the Secret. When renewal time arrives, cert-manager repeats the cycle without human intervention.

Ingress + cert-manager TLS FlowClientHTTPS requestIngressControllerTLS Secrettls.crt + tls.keycert-managerControllerLet's EncryptACME CAServiceClusterIP backendApplication Pods
Kubernetes Ingress and TLS with cert-manager: the Ingress controller terminates HTTPS using Secrets that cert-manager renews from Let's Encrypt.

If you already manage TLS on bare-metal Ubuntu with Certbot, the mental model maps cleanly. cert-manager is Certbot as a Kubernetes operator. The Ingress controller is Nginx or Apache at the edge. The difference is everything is declared in YAML and reconciled continuously, which fits a GitOps with ArgoCD workflow well.

How do you install an Ingress controller and cert-manager?

Start with a working cluster. Minikube, kind, k3s, EKS, and GKE all support this stack. You need kubectl access and Helm 3 installed locally. Pick one Ingress controller and one cert-manager release — mixing two controllers without clear IngressClass separation causes routing conflicts.

Install cert-manager

cert-manager ships official Helm charts. Pin a version rather than floating latest in production. The chart installs CRDs for Certificate, CertificateRequest, Issuer, ClusterIssuer, and Order resources.

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 \
  --version v1.16.2

kubectl get pods -n cert-manager

Wait until cert-manager, cert-manager-webhook, and cert-manager-cainjector pods reach Running. Webhook failures during Issuer creation usually mean the webhook pod is not ready yet.

Install an Ingress controller

ingress-nginx remains the most common choice on self-managed clusters. Cloud load-balancer Ingress controllers on EKS and GKE integrate with their respective LBs. Match your IngressClass to the controller you install.

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.ingressClassResource.name=nginx \
  --set controller.ingressClassResource.controllerValue=k8s.io/ingress-nginx

kubectl get svc -n ingress-nginx

Point your DNS A or CNAME record at the external IP or hostname of the controller Service. On bare metal without a cloud LB, MetalLB on bare-metal Kubernetes can advertise a pool IP. Certificate issuance will fail until DNS resolves to the cluster edge.

How do you configure a ClusterIssuer for Let's Encrypt?

An Issuer is namespace-scoped. A ClusterIssuer is cluster-wide and the usual choice for shared edge TLS. Define one ClusterIssuer for staging and one for production while testing. Let's Encrypt staging avoids rate limits during iteration.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx

Apply it and verify the Issuer reaches Ready status:

kubectl apply -f clusterissuer-letsencrypt-prod.yaml
kubectl describe clusterissuer letsencrypt-prod

The http01 solver tells cert-manager to create temporary Ingress rules so Let's Encrypt can reach /.well-known/acme-challenge/ on port 80. Port 80 must be open and routed through your Ingress controller. Blocking HTTP while forcing HTTPS-only breaks HTTP-01 validation unless you use DNS-01 instead.

ACME HTTP-01 Challenge Flow1. CertificateCR created2. ACME Order+ Challenge3. Temp Ingressfor token path4. CA validatesHTTP GET5. TLS Secretwritten to NS6. Ingress usestls secret refCommon failure pointsDNS not pointing to Ingress LBWrong ingressClassName on solver IngressPort 80 blocked or redirected too early
HTTP-01 ACME flow used by cert-manager: each step must succeed before Let's Encrypt signs the certificate.

For wildcard certificates covering *.example.com, HTTP-01 cannot work. Use DNS-01 with your DNS provider's API credentials stored in a Kubernetes Secret. Route53, Cloudflare, and DigitalOcean all have documented cert-manager webhook or native solver support. Treat DNS API tokens as sensitive — follow the same discipline as Kubernetes Secrets and ConfigMaps done right.

How do you attach TLS to an Ingress resource?

Two patterns exist. The annotation pattern is simpler for single Ingress objects. The Certificate CR pattern gives explicit control over SANs, duration, and renewBefore windows.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: laravel-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: laravel-app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: laravel-app
            port:
              number: 80

cert-manager sees the annotation, creates a Certificate named after the Secret, and manages the full lifecycle. The Secret name in spec.tls must match what cert-manager expects. Renaming the Secret orphan old Certificates and triggers re-issuance.

Explicit Certificate resource

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: laravel-app-cert
  namespace: production
spec:
  secretName: laravel-app-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - app.example.com
  - api.example.com
  renewBefore: 720h

Use explicit Certificate objects when one Secret covers multiple hostnames or when GitOps separates cert lifecycle from Ingress routing. On a Laravel app on Kubernetes, you typically terminate TLS at Ingress and run HTTP inside the pod — no app-level cert needed.

After applying, watch progress:

kubectl describe certificate laravel-app-tls -n production
kubectl describe certificaterequest -n production
kubectl get challenges -n production

A Certificate in Ready=True with a populated Secret means HTTPS is live. Test with curl:

curl -vI https://app.example.com

Confirm the issuer is Let's Encrypt and check expiry with openssl if needed. For cipher and protocol hardening after issuance, see TLS 1.3 vs 1.2 configuration for Nginx.

Which Ingress controller should you use with cert-manager?

cert-manager is controller-agnostic. It creates solver Ingress objects with the ingress class you specify in the ClusterIssuer. Your production controller must honor that class and expose ports 80 and 443 correctly.

ControllerBest forcert-manager notesOps complexity
ingress-nginxSelf-managed clusters, bare metal, DO/LinodeMature HTTP-01 solver support; widely documentedMedium — you manage upgrades
Traefikk3s default, edge microservicesNative IngressRoute CRD option; ACME can bypass cert-managerLow on k3s; medium elsewhere
AWS Load Balancer ControllerEKS with ALB/NLBDNS-01 common for wildcards; HTTP-01 needs correct target groupsMedium — IAM and SG wiring
GCE/GKE IngressGKE nativeGoogle-managed certs alternative; cert-manager for portable YAMLLow on GKE; cloud-specific
HAProxy IngressHigh-throughput L7 routingWorks with cert-manager; smaller community than nginxMedium

For lightweight edge clusters, k3s lightweight Kubernetes ships Traefik by default. You can keep Traefik and still run cert-manager, or disable Traefik and install ingress-nginx for consistency with larger clusters. Pick one edge stack and document it — switching controllers mid-project forces certificate re-validation.

HTTP-01 vs DNS-01 ChallengeHTTP-01Port 80 must reach IngressSingle hostname per certNo wildcard supportSimple setupNo DNS API token neededBest for public web appsLaravel, WordPress, APIsMost common choiceDNS-01TXT record at DNS hostWildcard certs supportedWorks behind firewallsNeeds DNS provider APIToken rotation requiredBest for internal toolsMulti-subdomain SAN certsUse when HTTP-01 fails
Choose HTTP-01 for standard Ingress TLS; use DNS-01 when you need wildcards or cannot expose port 80.

How do you troubleshoot cert-manager and Ingress TLS failures?

Most failures fall into a short list. DNS misconfiguration, wrong ingress class, rate limits, and premature HTTPS redirects cover the majority of support tickets I have seen on production clusters.

  1. Check Certificate status: kubectl describe certificate <name> — read Events at the bottom for the exact ACME error.
  2. Inspect Challenge objects: Pending challenges show the URL Let's Encrypt is trying to reach. curl that URL from outside the cluster.
  3. Verify DNS: dig app.example.com +short must return your Ingress LB IP before ordering a cert.
  4. Confirm ingress class: solver Ingress class must match your controller's IngressClass resource name.
  5. Test with staging first: swap ClusterIssuer server to https://acme-staging-v02.api.letsencrypt.org/directory during setup.
  6. Review controller logs: kubectl logs -n ingress-nginx deploy/ingress-nginx-controller for routing errors on challenge paths.

A Challenge stuck in pending with 403 or 404 usually means the temporary Ingress rule is not reaching the cert-manager solver pod. A 400 from Let's Encrypt often means stale authorization — delete the Certificate and Challenge objects to force a clean retry. For deeper pod-level issues on the same cluster, the guide on debugging CrashLoopBackOff in Kubernetes helps isolate solver pod crashes.

Let's Encrypt production rate limits matter on large migrations. You get 50 certificates per registered domain per week. Bulk-moving 80 subdomains in one afternoon hits the cap. Stage with the staging issuer, then batch production cutover across days. Official limits are documented at Let's Encrypt rate limits.

Renewal and expiry monitoring

cert-manager renews certificates when roughly two-thirds of the lifetime remains. Default cert duration from Let's Encrypt is 90 days, so renewal typically starts around day 60. Failed renewals leave the old cert in place until it expires — then browsers show errors.

90-Day Certificate Renewal TimelineIssuedDay 0Renew windowDay 60ExpiryDay 90Monitor with Prometheus metricscertmanager_certificate_expiration_timestamp_secondsAlert if < 14 days and not Ready=True
cert-manager renews Kubernetes Ingress TLS certificates before the 90-day Let's Encrypt expiry — monitor the window starting around day 60.

Export cert-manager metrics to Prometheus and alert on certificates expiring within 14 days. Pair this with Velero backup and restore for Kubernetes so you can recover cluster state if a bad Issuer change breaks all active certs at once. For EKS workloads, also review running Kubernetes on AWS with Amazon EKS because ALB annotations interact with Ingress TLS differently than bare ingress-nginx.

Store ClusterIssuer manifests and Certificate CRs in Git. A bad edit to solvers or email can silently break renewal for every hostname. Peer review those changes the same way you review application deploys. If your team lacks dedicated platform engineers, Linux system administration support and ongoing maintenance services cover cluster edge cases including cert-manager upgrades.

When moving a live site from a VM to Kubernetes, plan DNS TTL reduction a day ahead. Lower TTL to 300 seconds before cutover so you can roll back quickly if ACME validation fails. This mirrors the discipline used in website migration projects where downtime and cert gaps are the main client-facing risks.

For JSON manifest review before apply, a quick pass through the JSON formatter tool catches trailing commas and quoting errors that kubectl would reject. On booking platforms like Adventure Third Pole Trek, HTTPS at the Ingress edge is non-negotiable for payment trust — cert-manager removes the manual renewal step that VM-hosted Certbot required.

Domain registration and initial DNS setup must be correct before any ACME order. If you manage client domains, coordinate with domain registration and hosting services so nameservers and A records propagate before the first Certificate apply. A highly available Kubernetes control plane does not help if external DNS still points at the old server.

cert-manager upgrades require CRD migration attention. Read the release notes on cert-manager official documentation before bumping minor versions. The upstream Kubernetes Ingress documentation remains the reference for networking API field behavior across cluster versions.

Key Takeaways

  • Install cert-manager and one Ingress controller first; verify DNS points at the LB before requesting production certs.
  • Use a staging ClusterIssuer during setup to avoid Let's Encrypt rate limits while debugging HTTP-01 paths.
  • Annotate Ingress with cert-manager.io/cluster-issuer for simple apps; use Certificate CRs for multi-hostname or wildcard TLS.
  • Keep port 80 open for HTTP-01 unless you switch to DNS-01 with a properly scoped API token Secret.
  • Monitor certmanager_certificate_expiration_timestamp_seconds and alert well before the 90-day expiry window.
  • Version ClusterIssuer and Certificate YAML in Git and treat solver changes as production-impacting operations.

People Also Ask

Does cert-manager work with any Ingress controller?

Yes. cert-manager creates temporary Ingress or HTTPRoute resources using the ingress class defined in your ClusterIssuer solver config. As long as that class routes port 80 and 443 to the correct controller, ACME HTTP-01 validation succeeds. The controller brand does not matter — configuration alignment does.

Can cert-manager issue wildcard TLS certificates?

Wildcard certificates require DNS-01 validation because HTTP-01 cannot prove control over an entire subdomain tree. Configure a DNS solver with your provider API credentials in a Kubernetes Secret, then list *.example.com in the Certificate dnsNames field. Wildcard certs cover one level only — *.example.com does not include nested hosts like a.b.example.com.

What happens when a cert-manager certificate fails to renew?

The existing TLS Secret remains until the certificate expires. Browsers continue trusting the site until the notAfter date passes. After expiry, clients see certificate errors. cert-manager emits Kubernetes Events on the Certificate object and exposes Prometheus metrics you should alert on — do not rely on calendar reminders alone.

Is cert-manager better than certbot on Kubernetes?

On Kubernetes, cert-manager is the idiomatic choice. It watches Certificate CRs, integrates with Ingress annotations, and renews without shell access to nodes. Certbot inside a CronJob works but fights the declarative model and is harder to observe. cert-manager also centralises issuer config as cluster-level CRDs rather than scattered cron entries.

Put HTTPS on autopilot at the Ingress edge

Kubernetes Ingress and TLS with cert-manager turns HTTPS from a quarterly maintenance task into a reconciled cluster state. Install the controllers, define a ClusterIssuer, annotate your Ingress, and verify with describe and curl before you cut over DNS. Start with staging, watch Challenge objects during the first issuance, and add Prometheus alerts before production traffic depends on the cert. If you want help migrating a Laravel or WordPress workload from VM-based Certbot to a declarative cluster edge, contact us or browse the portfolio for platforms already running on production infrastructure.

Frequently Asked Questions

Ingress routes external HTTP and HTTPS to cluster Services and terminates TLS using Secrets. cert-manager automates those Secrets by ordering and renewing certificates from Let's Encrypt via ACME.

You need a working cluster, kubectl access, and Helm 3. Add the jetstack Helm repo, pin a chart version such as v1.16.2 rather than floating latest in production, and install with CRDs enabled into a cert-manager namespace. Wait until cert-manager, cert-manager-webhook, and cert-manager-cainjector pods reach Running before creating Issuers. Webhook failures during Issuer creation usually mean the webhook pod is not ready yet. Verify with kubectl get pods -n cert-manager before proceeding to Ingress controller installation.

Pick one controller and match its IngressClass to your ClusterIssuer solver config — mixing two without clear IngressClass separation causes routing conflicts. For self-managed clusters, ingress-nginx is the most common choice via the official Helm chart, setting controller.ingressClassResource.name to nginx. Point DNS A or CNAME records at the controller Service external IP or hostname. On bare metal without a cloud load balancer, MetalLB can advertise a pool IP. Certificate issuance fails until DNS resolves to the cluster edge, so verify resolution before requesting production certs.

An Issuer is namespace-scoped and only manages certificates within that namespace. A ClusterIssuer is cluster-wide and is the usual choice for shared edge TLS across multiple namespaces. For Let's Encrypt, both use the same ACME spec structure with server URL, contact email, privateKeySecretRef, and solvers. Most production setups define one staging ClusterIssuer and one production ClusterIssuer at the cluster level, then reference the production issuer from Ingress annotations or Certificate resources in any namespace.

Define a ClusterIssuer with acme.server set to https://acme-v02.api.letsencrypt.org/directory, a valid ops email, privateKeySecretRef for the ACME account key, and an http01 solver pointing at your Ingress class such as nginx. Apply the manifest and confirm Ready status with kubectl describe clusterissuer. The http01 solver creates temporary Ingress rules so Let's Encrypt reaches /.well-known/acme-challenge/ on port 80. During initial setup, create a parallel staging ClusterIssuer using the staging ACME directory to avoid production rate limits while debugging validation paths.

HTTP-01 tells cert-manager to create temporary Ingress rules so Let's Encrypt validates ownership over port 80 at a well-known challenge path. Port 80 must be open and routed through your Ingress controller; blocking HTTP while forcing HTTPS-only breaks HTTP-01 unless you adjust redirects. DNS-01 validates via TXT records at your DNS provider and is required for wildcard certificates covering subdomains like *.example.com, which HTTP-01 cannot issue. Route53, Cloudflare, and DigitalOcean have documented solver support. Store DNS API tokens in Kubernetes Secrets with the same sensitivity as other credentials.

Two patterns exist. The annotation pattern adds cert-manager.io/cluster-issuer to Ingress metadata, defines tls hosts and secretName, and lets cert-manager create a Certificate automatically — recommended for most single-app setups like a Laravel API. The explicit Certificate CR pattern gives control over SANs, duration, and renewBefore windows, useful when one Secret covers multiple hostnames or GitOps separates cert lifecycle from routing. After applying, watch kubectl describe certificate, certificaterequest, and challenges until Ready=True. Terminate TLS at Ingress and run HTTP inside pods; no app-level cert is needed.

Yes. cert-manager is controller-agnostic and creates solver Ingress objects using the ingress class specified in your ClusterIssuer solver config. ingress-nginx, Traefik on k3s, AWS Load Balancer Controller on EKS, GCE Ingress on GKE, and HAProxy Ingress all work as long as the solver class matches your production controller and ports 80 and 443 route correctly. The controller brand does not matter — configuration alignment does. Pick one edge stack, document it, and avoid switching controllers mid-project because that forces certificate re-validation.

For self-managed clusters, bare metal, and providers like DigitalOcean or Linode, ingress-nginx offers mature HTTP-01 solver support and wide documentation at medium ops complexity. Traefik ships as k3s default with lower complexity on lightweight edge clusters. AWS Load Balancer Controller suits EKS with DNS-01 common for wildcards. GCE Ingress on GKE offers Google-managed cert alternatives but cert-manager keeps YAML portable. HAProxy Ingress handles high-throughput L7 routing with a smaller community. Choose HTTP-01 for standard Ingress TLS and DNS-01 when you need wildcards or cannot expose port 80.

Most failures trace to a short list: DNS misconfiguration, wrong ingress class, Let's Encrypt rate limits, or premature HTTPS redirects blocking HTTP-01 challenge paths. Certificate issuance also fails until DNS A or CNAME records resolve to your Ingress load balancer IP. A Challenge stuck pending with 403 or 404 usually means the temporary solver Ingress rule is not reaching the cert-manager solver pod. A 400 from Let's Encrypt often indicates stale authorization — delete Certificate and Challenge objects to force a clean retry. Always test with a staging ClusterIssuer first.

Start with kubectl describe certificate and read Events at the bottom for the exact ACME error. Inspect Challenge objects and curl the challenge URL from outside the cluster. Verify DNS with dig returning your Ingress LB IP. Confirm the solver Ingress class matches your controller's IngressClass resource name. Swap to the staging ACME server during setup to isolate rate-limit issues. Review ingress-nginx controller logs for routing errors on challenge paths. For solver pod crashes, apply standard Kubernetes pod debugging. On large migrations, batch production cutover across days because Let's Encrypt allows 50 certificates per registered domain per week.

cert-manager renews when roughly two-thirds of the certificate lifetime remains. Let's Encrypt default duration is 90 days, so renewal typically starts around day 60. Failed renewals leave the old certificate in place until expiry, then browsers show errors.

Export cert-manager metrics to Prometheus and alert on certmanager_certificate_expiration_timestamp_seconds when certificates expire within 14 days. The renewal window starting around day 60 is when silent failures become visible if solvers or Issuer config broke. Pair monitoring with Velero backup and restore so you can recover cluster state if a bad Issuer edit breaks renewal for every hostname. Store ClusterIssuer manifests and Certificate CRs in Git and peer-review solver changes the same way you review application deploys — a bad edit can silently break renewal cluster-wide.

Yes. Port 80 must be open and routed through your Ingress controller for HTTP-01. Let's Encrypt reaches /.well-known/acme-challenge/ over plain HTTP. Forcing HTTPS-only without allowing challenge paths breaks validation unless you switch to DNS-01.

Yes, especially during initial setup and large migrations. Define separate ClusterIssuer resources for staging and production. The staging server at acme-staging-v02 avoids rate limits while you debug HTTP-01 paths, ingress class mismatches, and DNS propagation. Production limits allow 50 certificates per registered domain per week, so bulk-moving many subdomains in one afternoon hits the cap. Stage fully against the staging issuer, validate curl and openssl checks, then batch production cutover across days. Lower DNS TTL to 300 seconds before cutover so you can roll back quickly if ACME validation fails.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: