Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Cert Manager Ubuntu
GUIDEInstall Guides

How to Install cert-manager on Ubuntu 24.04 VPS: Automatic TLS Certificates for Kubernetes

26 min read

How to Install cert-manager on Ubuntu 24.04 VPS: Automatic TLS Certificates for Kubernetes

Manually renewing TLS certificates is a leading cause of avoidable outages. cert-manager removes the problem entirely: it issues, renews, and rotates certificates inside your Kubernetes cluster as native Kubernetes resources, and it is free for unlimited domains when paired with Let's Encrypt. This guide walks you through installing cert-manager on an Ubuntu 24.04 VPS running Kubernetes (k3s or full k8s), configuring ClusterIssuers for Let's Encrypt staging and production, issuing your first certificate, and wiring it into an Ingress with a single annotation.

Need a Kubernetes-ready VPS? The CloudCore Starter plan at EUR 7.99/month gives you 4 vCPU, 6 GB RAM and 100 GB NVMe — plenty to run k3s, cert-manager and a handful of Ingress-backed workloads.

Table of Contents

  • What is cert-manager?
  • Why Self-Host Free Automatic TLS?
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Install Helm
  • Step 3: Install cert-manager via Helm
  • Step 4: Verify the Installation
  • Step 5: Create a Let's Encrypt Staging ClusterIssuer
  • Step 6: Create a Let's Encrypt Production ClusterIssuer
  • Step 7: Issue Your First Certificate (HTTP-01)
  • Step 8: Wire Certificates into an Ingress
  • Step 9: Configure DNS-01 with Cloudflare
  • Step 10: Issue a Wildcard Certificate
  • Monitoring and Renewal
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is cert-manager?

    cert-manager is the de-facto certificate controller for Kubernetes. It extends the Kubernetes API with custom resources — Issuer, ClusterIssuer, Certificate, CertificateRequest, Order, and Challenge — and runs a reconciliation loop that turns declarative certificate requests into real, signed TLS certificates stored as Kubernetes Secret objects. Once a Certificate resource exists, cert-manager handles everything: generating the private key, building the CSR, solving the ACME challenge, fetching the signed certificate, and renewing it 30 days before expiry.

    cert-manager supports a broad list of certificate sources. On the public CA side, it speaks ACME natively and therefore works with Let's Encrypt, ZeroSSL, Buypass Go SSL, Google Public CA, and any other ACME v2 endpoint. For private PKI, it integrates with HashiCorp Vault, Venafi TPP/TLS Protect Cloud, AWS Certificate Manager Private CA, Google Cloud Certificate Authority Service, and self-signed CAs for internal services. It can also act as its own CA to issue internal certificates for service mesh workloads (Istio, Linkerd) and mTLS between microservices.

    The typical use cases are straightforward. Platform teams use cert-manager to give every developer's Ingress a working TLS certificate without tickets, spreadsheets, or shared passwords. SaaS operators use it to auto-issue per-customer certificates on custom domains — the exact workflow behind multi-tenant platforms that offer "bring your own domain". DevOps teams use it to issue short-lived internal certificates for mTLS inside the cluster, rotating them every few hours. And regulated environments use it with Vault or a private CA to meet compliance requirements while keeping the same declarative Kubernetes-native workflow.

    If you are running Kubernetes — whether it is k3s on a single VPS, managed GKE/EKS/AKS, or bare-metal k8s — cert-manager is almost always the correct answer for TLS.

    Why Self-Host Free Automatic TLS?

    Commercial certificate vendors still charge USD 50-500 per domain per year for DV and OV certificates, plus extra for wildcards and renewal services. cert-manager with Let's Encrypt eliminates the entire line item while removing the human-in-the-loop renewal step that causes outages. Concrete benefits:

    • Truly free — Let's Encrypt issues DV certificates at no cost, with no volume caps beyond the public rate limits (50 certificates per registered domain per week, which is more than almost anyone needs).
    • Fully automatic renewal — cert-manager renews 30 days before expiry by default. You never touch a cert again after the Certificate resource is created.
    • Declarative and GitOps-friendly — Certificates live in YAML, alongside your Deployments and Ingresses. They flow through Argo CD, Flux, and PR reviews like any other manifest.
    • Wildcards included — With DNS-01 (for example via Cloudflare), you can issue *.example.com in the same pipeline with no extra cost.
    • No single point of failure — No shared admin portal, no expiring credit card, no forgotten renewal emails. The controller runs inside the cluster and keeps working.
    • Ingress-native integration — One annotation on your Ingress and the certificate appears. Works with NGINX Ingress, Traefik, HAProxy, Contour, Istio Gateway, and others.
    • Short-lived internal certs for mTLS — For zero-trust workloads, cert-manager can issue hours- or minutes-long certificates from a private CA, rotated automatically.

    Cost Comparison: cert-manager vs. Paid Certificate Providers

    ScenarioPaid DV (e.g. DigiCert, Sectigo)Managed DNS+TLS SaaScert-manager + Let's Encrypt
    Cost per domain / yearUSD 50-150USD 20-60 (bundled)Free
    Wildcard certificateUSD 150-500 extraOften extraFree (DNS-01)
    Renewal workflowManual CSR + uploadVendor UIFully automatic
    GitOps / declarativeNoPartialYes (native CRDs)
    Unlimited subdomainsPer-cert pricingPlan-limitedFree
    Private CA / mTLSSeparate productRarely supportedIncluded
    Monthly cost at 20 domainsUSD 100-250USD 40-100EUR 7.99/mo (VPS only)
    For any team running more than two or three public endpoints, cert-manager pays for itself in the first renewal cycle.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • A running Kubernetes cluster — this guide assumes k3s (see our k3s install guide), but any cluster on v1.27+ works
    • kubectl configured and working — kubectl get nodes should return at least one Ready node
    • An Ingress controller installed — NGINX Ingress, Traefik, or similar (if you prefer a non-Kubernetes reverse proxy, see NGINX Proxy Manager)
    • A domain name with DNS records you can modify (A or CNAME pointing at your VPS)
    • Ports 80 and 443 open on the VPS firewall (required for HTTP-01 challenges)
    Recommended Plan: CloudCore Starter
    >
    For a single-node k3s cluster running cert-manager plus a handful of small workloads, the CloudCore Starter plan is the sweet spot:
    >
    - 4 vCPU cores
    - 6 GB RAM
    - 100 GB NVMe SSD
    - Unmetered bandwidth
    - EUR 7.99/month
    >
    Upgrade to CloudCore Professional later if you run more Ingresses and heavier workloads — cert-manager itself uses very little RAM (around 50-150 MB across its pods).

    Connect to your server via SSH and confirm your cluster is healthy:

    bash
    ssh root@your-server-ip
    kubectl get nodes
    kubectl get pods -A

    Step 1: Update System Packages

    Start by updating your package index and upgrading installed packages. This ensures you have the latest security patches and that the Helm and kubectl client installs below pull stable dependencies.

    bash
    sudo apt update && sudo apt upgrade -y

    Expected output (abbreviated):

    text
    Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
    Reading package lists... Done
    Building dependency tree... Done
    Calculating upgrade... Done
    0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

    If the kernel was updated, reboot and reconnect:

    bash
    sudo reboot

    Step 2: Install Helm

    cert-manager is distributed as a Helm chart. Helm is the Kubernetes package manager — it takes the guesswork out of installing cert-manager's CRDs, RBAC rules, webhooks, and Deployments in the correct order.

    Install Helm using the official script:

    bash
    curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

    Expected output:

    text
    Downloading https://get.helm.sh/helm-v3.14.4-linux-amd64.tar.gz
    Verifying checksum... Done.
    Preparing to install helm into /usr/local/bin
    helm installed into /usr/local/bin/helm

    Verify the installation:

    bash
    helm version

    Expected output:

    text
    version.BuildInfo{Version:"v3.14.4", GitCommit:"...", GoVersion:"go1.21.9"}

    Add the Jetstack Helm repository (Jetstack is the company that maintains cert-manager) and refresh the local chart index:

    bash
    helm repo add jetstack https://charts.jetstack.io --force-update
    helm repo update

    Expected output:

    text
    "jetstack" has been added to your repositories
    Hang tight while we grab the latest from your chart repositories...
    ...Successfully got an update from the "jetstack" chart repository
    Update Complete. ⎈Happy Helming!⎈

    Step 3: Install cert-manager via Helm

    cert-manager ships CustomResourceDefinitions (CRDs) for Certificate, Issuer, ClusterIssuer, Order, Challenge, and CertificateRequest. You can install them either as part of the Helm release (simplest) or separately (recommended for production so that helm uninstall does not delete your certificates).

    For most setups, the one-liner install with --set crds.enabled=true is the right call:

    bash
    helm install cert-manager jetstack/cert-manager \
      --namespace cert-manager \
      --create-namespace \
      --version v1.15.3 \
      --set crds.enabled=true

    Expected output:

    text
    NAME: cert-manager
    LAST DEPLOYED: Thu Apr 16 10:15:00 2026
    NAMESPACE: cert-manager
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    NOTES:
    cert-manager v1.15.3 has been deployed successfully!

    In order to begin issuing certificates, you will need to set up a ClusterIssuer or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).

    More information on the different types of issuers and how to configure them can be found in our documentation:

    https://cert-manager.io/docs/configuration/

    The chart deploys three Deployments in the cert-manager namespace:

    • cert-manager — the main controller that watches Certificate and Issuer resources and drives the issuance state machine.
    • cert-manager-webhook — a validating and mutating admission webhook that enforces the schema of cert-manager CRDs.
    • cert-manager-cainjector — injects CA bundles into webhooks, APIServices, and CRDs that need them.

    Optional Install Tuning

    For production clusters, pin resource limits and enable Prometheus metrics with a values file:

    bash
    cat > cert-manager-values.yaml <<EOF
    crds:
      enabled: true
    replicaCount: 2
    resources:
      requests:
        cpu: 10m
        memory: 64Mi
      limits:
        memory: 256Mi
    prometheus:
      enabled: true
      servicemonitor:
        enabled: true
    webhook:
      replicaCount: 2
    cainjector:
      replicaCount: 2
    EOF

    helm upgrade --install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.15.3 \ -f cert-manager-values.yaml

    This bumps each Deployment to two replicas for HA, caps memory, and exposes a ServiceMonitor for Prometheus Operator to scrape.

    Step 4: Verify the Installation

    Confirm all three cert-manager Deployments are ready:

    bash
    kubectl get pods -n cert-manager

    Expected output:

    text
    NAME                                       READY   STATUS    RESTARTS   AGE
    cert-manager-5c6866597d-abcde              1/1     Running   0          2m
    cert-manager-cainjector-6c94df44f7-fghij   1/1     Running   0          2m
    cert-manager-webhook-d4f79d7c7-klmno       1/1     Running   0          2m

    Check the CRDs were installed:

    bash
    kubectl get crd | grep cert-manager

    Expected output:

    text
    certificaterequests.cert-manager.io      2026-04-16T10:15:01Z
    certificates.cert-manager.io             2026-04-16T10:15:01Z
    challenges.acme.cert-manager.io          2026-04-16T10:15:01Z
    clusterissuers.cert-manager.io           2026-04-16T10:15:01Z
    issuers.cert-manager.io                  2026-04-16T10:15:01Z
    orders.acme.cert-manager.io              2026-04-16T10:15:01Z

    Smoke-test the webhook by creating a self-signed test certificate:

    bash
    cat <<EOF | kubectl apply -f -
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
      name: test-selfsigned
      namespace: default
    spec:
      selfSigned: {}
    
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: selfsigned-cert
      namespace: default
    spec:
      dnsNames:
        - example.com
      secretName: selfsigned-cert-tls
      issuerRef:
        name: test-selfsigned
    EOF

    Wait a few seconds then confirm the certificate is Ready=True:

    bash
    kubectl get certificate selfsigned-cert

    Expected output:

    text
    NAME              READY   SECRET                AGE
    selfsigned-cert   True    selfsigned-cert-tls   5s

    Clean up the smoke test:

    bash
    kubectl delete certificate selfsigned-cert
    kubectl delete issuer test-selfsigned
    kubectl delete secret selfsigned-cert-tls

    Step 5: Create a Let's Encrypt Staging ClusterIssuer

    Always start with Let's Encrypt staging. Production Let's Encrypt enforces strict rate limits (five duplicate certificates per week, 50 certs per registered domain per week). If you misconfigure your DNS or Ingress and spin a retry loop, you will exhaust the quota and be locked out for a week. Staging has much looser limits, issues certificates from a fake root that browsers will not trust, and lets you iterate safely.

    Create letsencrypt-staging-clusterissuer.yaml:

    yaml
    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-account-key
        solvers:
          - http01:
              ingress:
                ingressClassName: nginx

    Apply it:

    bash
    kubectl apply -f letsencrypt-staging-clusterissuer.yaml

    Verify the issuer registered successfully with Let's Encrypt:

    bash
    kubectl get clusterissuer letsencrypt-staging

    Expected output:

    text
    NAME                  READY   AGE
    letsencrypt-staging   True    15s

    If READY shows False, describe the resource to see why — usually a missing Ingress controller, wrong ingressClassName, or firewall blocking outbound HTTPS to Let's Encrypt:

    bash
    kubectl describe clusterissuer letsencrypt-staging

    Replace nginx in ingressClassName with traefik if you are running Traefik instead.

    Step 6: Create a Let's Encrypt Production ClusterIssuer

    Once staging works end-to-end for a test domain (next step), create the production issuer. The only difference is the ACME server URL.

    Create letsencrypt-prod-clusterissuer.yaml:

    yaml
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod
    spec:
      acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: [email protected]
        privateKeySecretRef:
          name: letsencrypt-prod-account-key
        solvers:
          - http01:
              ingress:
                ingressClassName: nginx

    Apply:

    bash
    kubectl apply -f letsencrypt-prod-clusterissuer.yaml
    kubectl get clusterissuer letsencrypt-prod

    Expected output:

    text
    NAME               READY   AGE
    letsencrypt-prod   True    8s

    You now have two ClusterIssuers. Any namespace can reference them by name. The convention across the cert-manager community is to always test with letsencrypt-staging first, then switch the issuerRef to letsencrypt-prod only once you have confirmed the order succeeds.

    Step 7: Issue Your First Certificate (HTTP-01)

    The HTTP-01 challenge works by Let's Encrypt sending an HTTP request to http://yourdomain.com/.well-known/acme-challenge/<token>. cert-manager creates a temporary Ingress and Service pointing to a solver Pod that serves the expected token. As long as your domain's A record points at the VPS and port 80 is open, the challenge completes in a few seconds.

    Ensure DNS is pointing correctly:

    bash
    dig +short example.com
    

    should return your VPS public IP

    Create a Certificate resource in the default namespace:

    yaml
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: example-com-tls
      namespace: default
    spec:
      secretName: example-com-tls
      issuerRef:
        name: letsencrypt-staging
        kind: ClusterIssuer
      dnsNames:
        - example.com
        - www.example.com

    Apply it:

    bash
    kubectl apply -f example-com-certificate.yaml

    Watch the issuance progress:

    bash
    kubectl get certificate,order,challenge -n default

    Expected output during issuance:

    text
    NAME                                         READY   SECRET            AGE
    certificate.cert-manager.io/example-com-tls  False   example-com-tls   20s

    NAME STATE AGE order.acme.cert-manager.io/example-com-tls-1-1234567890 pending 20s

    NAME STATE DOMAIN AGE challenge.acme.cert-manager.io/example-com-tls-1-1234567890-1-2345678901 pending example.com 20s

    After 20-60 seconds, the certificate should become ready:

    text
    NAME                                          READY   SECRET            AGE
    certificate.cert-manager.io/example-com-tls   True    example-com-tls   2m

    Inspect the resulting Secret — it contains tls.crt and tls.key:

    bash
    kubectl describe secret example-com-tls -n default

    If everything worked against staging, edit the Certificate and change issuerRef.name to letsencrypt-prod:

    bash
    kubectl edit certificate example-com-tls -n default

    Then delete the staging Secret so cert-manager re-issues from production:

    bash
    kubectl delete secret example-com-tls -n default

    Step 8: Wire Certificates into an Ingress

    In practice, you rarely create Certificate resources by hand. Instead, you annotate the Ingress and cert-manager creates the Certificate for you. This is called the Ingress shim.

    Example Ingress with automatic TLS:

    yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: myapp
      namespace: default
      annotations:
        cert-manager.io/cluster-issuer: letsencrypt-prod
    spec:
      ingressClassName: nginx
      tls:
        - hosts:
            - app.example.com
          secretName: myapp-tls
      rules:
        - host: app.example.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: myapp-service
                    port:
                      number: 80

    Apply it:

    bash
    kubectl apply -f myapp-ingress.yaml

    cert-manager sees the cert-manager.io/cluster-issuer annotation and the tls block, then automatically creates a Certificate named myapp-tls in the same namespace. Within a minute, https://app.example.com serves a valid Let's Encrypt certificate.

    Supported annotations:

    • cert-manager.io/cluster-issuer: letsencrypt-prod — use a ClusterIssuer (cluster-wide).
    • cert-manager.io/issuer: letsencrypt-prod — use a namespaced Issuer.
    • cert-manager.io/common-name: app.example.com — override the CN (rarely needed).
    • cert-manager.io/duration: 2160h — request a specific lifetime (default 90 days for Let's Encrypt; not all CAs honour this).
    • cert-manager.io/renew-before: 720h — renew this many hours before expiry (default 30 days).
    If you manage your proxy outside Kubernetes with NGINX Proxy Manager, you do not need cert-manager — NPM has its own built-in Let's Encrypt client. Use cert-manager specifically for in-cluster Ingresses.

    Step 9: Configure DNS-01 with Cloudflare

    HTTP-01 works beautifully for public HTTP endpoints, but it has two limitations:

  • It cannot issue wildcard certificates (*.example.com). Let's Encrypt only allows wildcards via DNS-01.
  • It requires port 80 to be reachable from the public internet. That is a non-starter for internal services that you still want Let's Encrypt certificates for.
  • DNS-01 solves both. cert-manager proves domain control by creating a TXT record at _acme-challenge.example.com, Let's Encrypt queries DNS to verify it, and the certificate issues. For DNS-01 you need API access to your DNS provider. Cloudflare is the simplest — a scoped API token takes 30 seconds to create.

    Create a Scoped Cloudflare API Token

  • Go to Cloudflare dashboard → My Profile → API Tokens → Create Token.
  • Use the Edit zone DNS template.
  • Under Zone Resources, restrict the token to the specific zone (example.com) — never give it access to "All zones".
  • Permissions should be exactly: Zone → Zone → Read and Zone → DNS → Edit.
  • Create the token and copy it immediately — Cloudflare shows it only once.
  • Store the Token as a Kubernetes Secret

    bash
    kubectl create secret generic cloudflare-api-token \
      --namespace cert-manager \
      --from-literal=api-token=YOUR_CLOUDFLARE_TOKEN_HERE

    Important: the Secret must live in the cert-manager namespace, not the namespace where your Certificates live. The cert-manager controller reads it directly.

    Create a ClusterIssuer Using DNS-01

    Create letsencrypt-prod-dns01.yaml:

    yaml
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod-dns01
    spec:
      acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: [email protected]
        privateKeySecretRef:
          name: letsencrypt-prod-dns01-account-key
        solvers:
          - dns01:
              cloudflare:
                apiTokenSecretRef:
                  name: cloudflare-api-token
                  key: api-token
            selector:
              dnsZones:
                - "example.com"

    The selector.dnsZones block ensures this solver is only used for the example.com zone. You can add multiple solvers to a single issuer — one per zone, or a mix of HTTP-01 and DNS-01.

    Apply:

    bash
    kubectl apply -f letsencrypt-prod-dns01.yaml
    kubectl get clusterissuer letsencrypt-prod-dns01

    Expected output:

    text
    NAME                     READY   AGE
    letsencrypt-prod-dns01   True    5s

    Step 10: Issue a Wildcard Certificate

    With DNS-01 configured, wildcard certificates are a one-line change:

    yaml
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: wildcard-example-com
      namespace: default
    spec:
      secretName: wildcard-example-com-tls
      issuerRef:
        name: letsencrypt-prod-dns01
        kind: ClusterIssuer
      dnsNames:
        - "example.com"
        - "*.example.com"

    Apply:

    bash
    kubectl apply -f wildcard-cert.yaml

    Watch the order progress:

    bash
    kubectl get certificate wildcard-example-com -n default -w

    DNS-01 is slightly slower than HTTP-01 because cert-manager has to wait for the TXT record to propagate before asking Let's Encrypt to verify. Expect 30-120 seconds typically, longer if your DNS provider has high TTLs.

    Once READY=True, the Secret wildcard-example-com-tls can be referenced by any Ingress in the default namespace:

    yaml
    spec:
      tls:
        - hosts:
            - app.example.com
            - api.example.com
            - admin.example.com
          secretName: wildcard-example-com-tls

    This is the ideal setup for multi-tenant SaaS platforms: one wildcard certificate, many subdomains, zero per-tenant certificate provisioning.

    Monitoring and Renewal

    cert-manager renews certificates 30 days before expiry by default, controlled by spec.renewBefore on the Certificate. The reconcile loop runs every 10 minutes, so renewals happen automatically with zero intervention.

    Check All Certificates at a Glance

    bash
    kubectl get certificate -A

    Expected output:

    text
    NAMESPACE    NAME                    READY   SECRET                    AGE
    default      example-com-tls         True    example-com-tls           45d
    default      wildcard-example-com    True    wildcard-example-com-tls  40d
    monitoring   grafana-tls             True    grafana-tls               12d

    Force an Immediate Renewal

    bash
    kubectl cert-manager renew example-com-tls -n default

    This requires the kubectl cert-manager plugin. Install it with krew:

    bash
    kubectl krew install cert-manager

    Prometheus Metrics

    If you enabled prometheus.enabled=true in the Helm values, cert-manager exposes:

    • certmanager_certificate_expiration_timestamp_seconds — per-certificate expiry time.
    • certmanager_certificate_ready_status — 1 if Ready=True.
    • certmanager_http_acme_client_request_count — ACME client request counter by status.
    A minimal alerting rule:

    yaml
    - alert: CertificateExpiringSoon
      expr: (certmanager_certificate_expiration_timestamp_seconds - time()) / 86400 < 14
      for: 1h
      labels:
        severity: warning
      annotations:
        summary: "Certificate {{ $labels.name }} expires in less than 14 days"

    Troubleshooting

    ProblemCauseSolution
    ClusterIssuer stays Ready=False with ACME account registration failedCluster cannot reach Let's Encrypt (firewall / outbound egress)Test: kubectl run -it --rm test --image=curlimages/curl -- curl -v https://acme-v02.api.letsencrypt.org/directory. Fix egress rules.
    Certificate stuck Ready=False, Challenge shows pending with Waiting for HTTP-01 challenge propagationPort 80 not reachable from internet, or wrong ingressClassNamecurl http://yourdomain.com/.well-known/acme-challenge/test from outside. Confirm ingressClassName matches your installed controller.
    Challenge error: Error presenting challenge: Cloudflare API error: UnauthorizedAPI token wrong, expired, or missing Zone:DNS:Edit permissionRecreate the token, double-check it was stored in the cert-manager namespace, not the app namespace.
    Order fails with too many certificates already issued for exact set of domainsLet's Encrypt production rate limit (5 duplicates per week)Wait 7 days, or use staging during testing. Never iterate against production.
    Certificate renewed but Ingress still serves old certIngress controller has cached the old SecretRestart the Ingress controller Pods, e.g. kubectl rollout restart deploy/ingress-nginx-controller -n ingress-nginx.
    Certificate annotation ignored on IngressIngress shim disabled or wrong annotation keyUse cert-manager.io/cluster-issuer (not certmanager.k8s.io/... — that key is deprecated).
    Webhook errors: x509: certificate signed by unknown authoritycainjector not running or crashedkubectl logs -n cert-manager deploy/cert-manager-cainjector. Restart the Deployment.

    Inspecting a Stuck Issuance

    cert-manager's state machine produces a chain of resources: Certificate → CertificateRequest → Order → Challenge. When something fails, describe each step to find the error message:

    bash
    kubectl describe certificate example-com-tls -n default
    kubectl describe certificaterequest -n default
    kubectl describe order -n default
    kubectl describe challenge -n default

    The failing Challenge almost always has the clearest error in its status.reason.

    Controller Logs

    bash
    kubectl logs -n cert-manager deploy/cert-manager -f

    Follow logs while you re-apply the Certificate to watch the issuance in real time.

    FAQ

    What is the difference between an Issuer and a ClusterIssuer?

    Issuer is namespaced — it can only issue certificates for Certificate resources in the same namespace. ClusterIssuer is cluster-scoped and can be referenced by any namespace. For a single shared Let's Encrypt account across the whole cluster, use ClusterIssuer. For per-team isolation (for example, different Vault mounts per team), use Issuer in each team's namespace. The spec is otherwise identical — ClusterIssuer simply lets you avoid duplicating the same ACME account config into every namespace.

    Do I need cert-manager if I use Traefik or NGINX Proxy Manager?

    If your reverse proxy is in Kubernetes and is serving cluster workloads, cert-manager is almost always the right answer — it is declarative, GitOps-friendly, and supports every ACME CA plus private CAs. If you are running a standalone reverse proxy outside Kubernetes (for example, Traefik as a systemd service or NGINX Proxy Manager in Docker), use the built-in Let's Encrypt client in those tools instead. Mixing cert-manager with a non-Kubernetes proxy adds complexity for no benefit.

    HTTP-01 vs. DNS-01 — which should I use?

    HTTP-01 is simpler to set up (no API tokens, works out of the box), requires port 80 open to the internet, and cannot issue wildcards. DNS-01 requires DNS provider API access but supports wildcards and works for internal services with no public HTTP endpoint. A good default: use HTTP-01 for public per-host certificates, and DNS-01 with Cloudflare for wildcard certificates or internal services. You can configure multiple solvers in a single ClusterIssuer using selector.dnsZones or selector.matchLabels.

    Can I run cert-manager on k3s or a single-node cluster?

    Yes — cert-manager is regularly run on k3s, kind, microk8s, and single-node production clusters. The default Helm install uses about 100-150 MB RAM across the three Deployments and practically no CPU at idle. On a single-node cluster you may want to reduce replicas to 1 for webhook and cainjector to save memory: --set webhook.replicaCount=1 --set cainjector.replicaCount=1. See our k3s install guide for the full stack.

    How does cert-manager handle renewals for certificates issued to Ingresses?

    When you annotate an Ingress with cert-manager.io/cluster-issuer, cert-manager creates a matching Certificate resource and then manages renewal on that Certificate. The renewal writes a fresh tls.crt and tls.key into the same Secret referenced by the Ingress tls.secretName field. Most Ingress controllers (NGINX, Traefik, HAProxy) watch their TLS Secrets and pick up the new certificate within seconds. If a controller caches the old certificate, a rolling restart of the controller Pods forces a reload. Renewals happen 30 days before expiry by default.

    Is cert-manager production-ready? Who uses it?

    cert-manager is a CNCF Graduated project — the same maturity level as Kubernetes, Prometheus, and Envoy. It is used in production by thousands of organisations including major SaaS platforms, banks, government deployments, and every major managed Kubernetes offering (GKE, EKS, AKS, DigitalOcean, Civo). For authoritative documentation and production best practices, consult the official docs at cert-manager.io/docs/.

    Next Steps

    Now that cert-manager is issuing certificates on your VPS, these are natural follow-ups:

    • Install an Ingress controller if you skipped it — See our Traefik install guide for a Kubernetes-native option, or NGINX Ingress for the community default.
    • Set up k3s properly for production — If you are running on a single VPS, follow the k3s install guide for persistence, backups, and firewall rules.
    • Add mTLS between services — Use cert-manager as an internal CA to issue short-lived certificates for pod-to-pod mTLS. Combine with Istio or Linkerd for full service mesh identity.
    • Automate Ingress creation with Helm charts — Package your applications as Helm charts where the Ingress annotation points at letsencrypt-prod. Every new release gets a certificate automatically.
    • Monitor certificate expiry with Prometheus and Alertmanager — Enable the ServiceMonitor, scrape cert-manager metrics, and alert on certmanager_certificate_ready_status == 0 to catch renewal failures before users do.
    • Adopt GitOps with Argo CD or Flux — Commit your ClusterIssuer and Certificate manifests to Git. Every environment gets identical TLS configuration, reviewable via pull request.

    Run cert-manager and Kubernetes on a VPS built for it
    >
    The CloudCore Starter plan at EUR 7.99/month gives you 4 vCPU, 6 GB RAM, 100 GB NVMe, and unmetered bandwidth — enough to comfortably run k3s, cert-manager, an Ingress controller, and multiple workloads with free automatic TLS for every domain you own.
    >
    - Full root access and sudo privileges
    - Ubuntu 24.04 LTS pre-installed
    - Public IPv4 with reverse DNS for Let's Encrypt HTTP-01
    - Unmetered bandwidth — no surprise bills during renewal storms
    - Upgrade to CloudCore Professional when your workloads outgrow Starter
    >
    Deploy Your Kubernetes VPS Now — Plans start at EUR 7.99/month.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket