Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Cert-Manager TLS

Manage TLS certificates with Cert-Manager in Kubernetes. This lesson covers automated certificate lifecycle, issuance, and renewal using Let's Encrypt, with hands-on exercises and troubleshooting.

Focus: manage tls certificates with cert-manager

Sponsored

Picture this: you've deployed your production application on Kubernetes and pointed a shiny domain at it, but visitors see ERR_CERT_AUTHORITY_INVALID — the dreaded red warning. Manually generating self-signed certificates or juggling renewals every 90 days with Let's Encrypt is not only tedious but also error-prone. That's exactly the pain Cert-Manager eliminates: it automates the entire lifecycle of TLS certificates inside your cluster, from request to renewal, so your ingress traffic is always encrypted without manual intervention.

The problem this lesson solves

Securing Kubernetes ingresses with HTTPS requires valid TLS certificates. While you can create TLS secrets manually using kubectl create secret tls, this approach does not scale:

  • Short-lived certificates (Let's Encrypt's 90-day validity) demand regular manual renewals.
  • Certificate revocation or expiry causes service outages or browser warnings.
  • Mixed environments (staging, production) require different issuers, complicating management.

Cert-Manager solves all of these by acting as a certificate operator — it watches your custom resources (Certificate, Issuer, ClusterIssuer) and automatically provisions, renews, and manages TLS secrets based on your policies.

Core concept / mental model

Think of Cert-Manager as a robot assistant that sits inside your Kubernetes cluster and talks to Certificate Authorities (CAs) on your behalf. You tell it: "I want a certificate for myapp.example.com that expires every 90 days, and please use Let's Encrypt as the issuer." Cert-Manager then:

  1. Sends a certificate signing request (CSR) to the CA.
  2. Completes the required domain validation challenge (HTTP-01 or DNS-01).
  3. Stores the resulting private key + certificate as a Kubernetes Secret.
  4. Automatically renews the secret before expiry (typically 30 days before).

Key resources involved:

  • Issuer / ClusterIssuer — defines which CA to trust (e.g., Let's Encrypt staging, production, internal CA). Use ClusterIssuer for cluster-wide certificates.
  • Certificate — the desired certificate specification (domain, secret name, issuer reference).
  • Order & Challenge — internal resources Cert-Manager uses to complete validation.

Pro tip: Always start with Let's Encrypt's staging environment — it has no rate limits for testing. Switch to production only after verifying issuance works correctly.

How it works step by step

The certificate lifecycle managed by Cert-Manager follows a predictable flow:

  1. Install Cert-Manager in its own namespace (cert-manager). It deploys controllers and webhook components.
  2. Create an Issuer or ClusterIssuer for your desired CA (e.g., Let's Encrypt ACME).
  3. Create a Certificate resource referencing the issuer and the domain(s) you need.
  4. Cert-Manager sends the CSR to the CA and presents the validation challenge.
  5. Upon successful validation, the CA signs the certificate, and Cert-Manager stores the result as a TLS Secret in the same namespace as the Certificate resource.
  6. Reference the secret in your Ingress or Gateway API resource.
  7. Cert-Manager watches the expiry and automatically renews the certificate (default: 2/3 of the validity period before expiry).

If using HTTP-01 challenge, your Ingress must be publicly reachable on port 80 and correctly route the challenge path (/.well-known/acme-challenge/) to the Cert-Manager solver pod. DNS-01 works even with private clusters but requires cloud DNS credentials.

Hands-on walkthrough

Let's install Cert-Manager and create a real TLS certificate for a domain you control. We'll use Let's Encrypt staging.

1. Install Cert-Manager

# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Install cert-manager with CRDs included
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.15.0 \
  --set installCRDs=true

Verify installation:

kubectl get pods --namespace cert-manager
# Expected output: cert-manager-xxx, cert-manager-webhook-xxx, cert-manager-cainjector-xxx (all Running)

2. Create a ClusterIssuer for Let's Encrypt staging

# staging-cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email for account registration
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        ingress:
          class: nginx

Apply it:

kubectl apply -f staging-cluster-issuer.yaml

3. Create a Certificate resource and an Ingress

# example-tls-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myapp-tls
  namespace: default
spec:
  secretName: myapp-tls-secret
  issuerRef:
    name: letsencrypt-staging
    kind: ClusterIssuer
  commonName: myapp.example.com
  dnsNames:
  - myapp.example.com
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.example.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: myapp-service
            port:
              number: 80
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls-secret

Apply both:

kubectl apply -f example-tls-certificate.yaml
kubectl apply -f ingress.yaml

4. Verify the certificate is issued

# Check the Certificate status
kubectl get certificate myapp-tls -o wide

# Check the secret was created
kubectl get secret myapp-tls-secret

# Describe to see the conditions
kubectl describe certificate myapp-tls

Expected output snippet from describe:

Status:
  Conditions:
    Last Transition Time:  2025-04-13T10:00:00Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready

Now your ingress automatically serves the TLS certificate. When it expires, Cert-Manager will renew it without any action from you.

Compare options / when to choose what

Approach Pros Cons Best for
Manual TLS Secret (kubectl create secret tls) No extra dependency, simple for dev No automatic renewal, manual expiry management Single-dev clusters, throwaway
Cert-Manager with HTTP-01 Zero config for public ingresses, fully automated Requires public IP:80 reachable, rate limited per domain Production with public load balancers
Cert-Manager with DNS-01 Works for private/internal domains, wildcard certs Requires cloud DNS API credentials, more complex setup Internal services, wildcard TLS
External CA (e.g., Vault) Full control, compliance Higher operational overhead, requires secrets management Enterprise with strict PKI policies

Variations to explore: - Use ClusterIssuer instead of namespace-scoped Issuer to share CA config across all namespaces. - Combine Cert-Manager with External DNS for fully automated DNS-01 (e.g., for wildcard certificates *.example.com). - For internal clusters, configure a custom CA cert as a Issuer (non-ACME).

Troubleshooting & edge cases

Certificate stuck in "pending" state

kubectl describe certificate myapp-tls

Look for events like: - Waiting on CertificateRequest → check the ACME Order: kubectl describe order -n default | grep -A10 -i status - Failed to verify domain → your Ingress must route /.well-known/acme-challenge/ to the solver pod. Ensure the ingress controller is working. - Rate limit exceeded → switch from Let's Encrypt prod to staging or wait.

HTTP-01 challenge fails on private networks

If your cluster is not reachable from the internet, HTTP-01 cannot work. Instead, use DNS-01 by: 1. Creating a secret with cloud DNS API credentials (e.g., AWS route53, Google Cloud DNS). 2. Configuring the ACME solver with dns01 provider.

Example snippet for AWS Route53:

solvers:
- dns01:
    route53:
      region: us-east-1
      accessKeyIDSecretRef:
        name: route53-credentials
        key: access-key-id
      secretAccessKeySecretRef:
        name: route53-credentials
        key: secret-access-key

Secret not created after certificate is issued

Ensure the secretName in the Certificate resource matches the secretName in the Ingress TLS section. Also check that the Secret namespace is the same as the Certificate resource.

Common mistakes: - Using the production Let's Encrypt URL for initial testing — you will hit rate limits quickly. Always start with staging. - Forgetting to annotate the Ingress with cert-manager.io/cluster-issuer — without this, Cert-Manager does not auto-issue certificates for the ingress. - Using a mismatched commonName vs dnsNames — Cert-Manager will issue only for listed dnsNames. commonName is deprecated but used as a fallback. - Not creating the Issuer in the same namespace as the Certificate (when using Issuer instead of ClusterIssuer).

What you learned & what's next

You now understand how to manage TLS certificates with Cert-Manager — from installation, configuring an ACME issuer, to automatic certificate provisioning for Ingress resources. You've seen how to troubleshoot common issues like pending orders and private network limitations.

Key takeaways: - Cert-Manager eliminates manual certificate management via Kubernetes-native custom resources. - Use ClusterIssuer for cluster-wide issuers and Certificate resources to define what you need. - HTTP-01 is the simplest for public ingresses; DNS-01 supports wildcards and private clusters. - Let's Encrypt staging is essential for testing without rate limits. - Annotate your Ingress to enable automatic certificate injection. - Always verify certificate readiness with kubectl describe certificate.

Next step: In the following lesson, you'll explore Secrets management with External Secrets Operator — extending automation beyond TLS to database credentials and API tokens. You'll integrate with cloud vaults like AWS Secrets Manager and GCP Secret Manager.

Practice recap

As a hands-on challenge, repeat the walkthrough but instead of HTTP-01, modify the ClusterIssuer to use DNS-01 with your cloud DNS provider (or simulate with a dummy credential). Create a wildcard Certificate for *.example.com and verify the TLS secret contains a matching wildcard. Finally, delete the Certificate and confirm the automatic cleanup removes the secret and ingress update.

Common mistakes

  • Using Let's Encrypt production URL for initial testing — always start with staging https://acme-staging-v02.api.letsencrypt.org/directory to avoid rate limits.
  • Forgetting to add the cert-manager.io/cluster-issuer annotation on the Ingress — without it, Cert-Manager does not automatically create or update the TLS secret.
  • Mismatching commonName and dnsNames — the certificate will only cover the domains listed in dnsNames; commonName is deprecated and ignored by modern browsers.
  • Using a namespace-scoped Issuer but creating the Certificate in a different namespace — the Certificate will never fetch the issuer configuration and will stay pending.

Variations

  1. Use DNS-01 challenge with cloud providers (AWS Route53, Google CloudDNS, Azure DNS) instead of HTTP-01 — enables wildcard certificates and works for private/internal clusters.
  2. Deploy a non-ACME Issuer using your own internal CA by providing a pre-generated CA certificate and key — useful for internal-only PKI without internet access.
  3. Integrate Cert-Manager with External Secrets Operator and cloud vaults for a fully automated, central certificate issuance pipeline across multiple clusters.

Real-world use cases

  • Automatically provision Let's Encrypt TLS certificates for every new production Ingress in a multi-tenant Kubernetes cluster without manual ops intervention.
  • Secure internal microservices with TLS using a private CA (via Cert-Manager) and DNS-01 challenges in a self-hosted data center cluster.
  • Deploy a wildcard certificate *.example.com for all subdomains of a SaaS platform, auto-renewed every 90 days, with zero downtime during renewal.

Key takeaways

  • Cert-Manager automates the entire lifecycle of TLS certificates: issuance, renewal, and secret management — no manual kubectl needed.
  • Two main custom resources: ClusterIssuer (cluster-wide CA config) and Certificate (domain + issuer reference).
  • HTTP-01 is the simplest challenge, but requires public ingress on port 80; DNS-01 works for private clusters and wildcard certs.
  • Always test with Let's Encrypt staging first, then switch to production to avoid rate limiting.
  • Annotate your Ingress with cert-manager.io/cluster-issuer to enable automatic certificate provisioning and injection.
  • Monitor certificate status with kubectl describe certificate and check Order/Challenge resources for debugging.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.