CertManager SSL Certificate Deployment Guide

Mar 26, 2026

What is CertManager?

CertManager is a tool that automatically creates, manages, and renews SSL/TLS certificates for your websites and services running inside a Kubernetes cluster. Think of it as a smart helper that keeps your padlock (the green lock icon in browsers) always up to date, so you never have to worry about certificates expiring.

???? Why does this matter?

Without CertManager: You must manually create, install, and renew every certificate yourself. If you forget, users see scary 'Not Secure' warnings.

With CertManager: Everything is automatic. Certificates are renewed 30 days before they expire — no human action needed.

? Before You Start

Make sure you have completed the CertManager Installation Guide before following this deployment guide.

You will need access to your Kubernetes cluster via kubectl commands.

You will also need a Cloudflare account (or another DNS provider) with API access.


Section 1 — Creating Your First SSL Certificate

There are two ways to create a certificate using CertManager. Choose the method that best fits your situation:

MethodWhen to Use It
Method 1 — Certificate ResourceYou want full control and specify every detail yourself (recommended for most users).
Method 2 — Ingress AnnotationYou want CertManager to do everything automatically when you set up a website route.

Method 1 — Certificate Resource (Manual / Declarative)

In this method you write a small configuration file that tells CertManager exactly what kind of certificate you need. CertManager then goes and fetches it for you.

Step 1A — Single Domain Certificate (one website)

Use this when you only need a certificate for one domain, for example: example.com.

Step 1 — Create the certificate file

Open a text editor and create a new file called certificate.yaml. Paste the following content, replacing the values shown in CAPITALS with your own details:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-cert # A name for this certificate (you choose)
namespace: default # The Kubernetes namespace to use
spec:
secretName: example-com-tls # Where the certificate will be stored
duration: 2160h # Certificate is valid for 90 days
renewBefore: 720h # Renew it 30 days before it expires
issuerRef:
name: custom-acme-issuer # Name of your ClusterIssuer
kind: ClusterIssuer
group: cert-manager.io
dnsNames:
- example.com # YOUR domain name
commonName: example.com # Same as above
privateKey:
algorithm: RSA
size: 2048
rotationPolicy: Always # Creates a new key every renewal

???? What do these settings mean?

duration: 2160h — The certificate will last 90 days (2160 hours).

renewBefore: 720h — CertManager will automatically renew it 30 days before it expires.

rotationPolicy: Always — Each time the certificate renews, a fresh security key is generated.

secretName — This is where Kubernetes stores the actual certificate files securely.

Step 2 — Apply (submit) the file to your cluster

Open your terminal and run this command:

kubectl apply -f certificate.yaml

Step 3 — Watch the certificate being created

Run this command to see live status updates:

kubectl get certificate example-com-cert -w

You will see output like this:

NAME               READY   SECRET            AGE
example-com-cert False example-com-tls 5s ? Still working...
example-com-cert True example-com-tls 45s ? Done! Certificate is ready.

? What to expect

False means CertManager is still requesting the certificate from the ACME server.

True means success! Your certificate is now stored and ready to use.

The process usually takes between 30 seconds and 2 minutes.

Step 4 — Verify the certificate details

kubectl describe certificate example-com-cert

Step 5 — View the stored certificate secret

kubectl get secret example-com-tls -o yaml

Step 6 — Extract the certificate files (optional)

If you need to download the actual certificate files to your computer, run these commands one by one:

# Save the certificate
kubectl get secret example-com-tls -o jsonpath='{.data.tls\.crt}' | base64 -d > cert.pem

# Save the private key
kubectl get secret example-com-tls -o jsonpath='{.data.tls\.key}' | base64 -d > key.pem

# Save the CA certificate
kubectl get secret example-com-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.pem

# Verify the certificate looks correct
openssl x509 -in cert.pem -text -noout

Step 1B — Multi-Domain Certificate (several websites)

Use this when you want one certificate that covers several different domain names, for example: example.com, www.example.com, mail.example.com.

Create a file called certificate-multi.yaml with the following content:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-multi-cert
namespace: default
spec:
secretName: example-multi-tls
duration: 2160h
renewBefore: 720h
issuerRef:
name: custom-acme-issuer
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
- mail.example.com
- api.example.com
kubectl apply -f certificate-multi.yaml

Step 1C — Wildcard Certificate (all subdomains at once)

A wildcard certificate covers ALL subdomains of your domain at once. For example, *.example.com covers www.example.com, api.example.com, app.example.com — and any other subdomain you might create in the future.

? Important Note

Wildcard certificates require DNS-01 challenge validation, which means your DNS provider (e.g. Cloudflare) must already be configured in CertManager.

Make sure your ClusterIssuer is set up correctly before proceeding.

Create a file called certificate-wildcard.yaml:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-cert
namespace: default
spec:
secretName: wildcard-example-tls
duration: 2160h
renewBefore: 720h
issuerRef:
name: custom-acme-issuer
kind: ClusterIssuer
dnsNames:
- "*.example.com" # Covers ALL subdomains
- example.com # Also covers the main domain
kubectl apply -f certificate-wildcard.yaml

Method 2 — Automatic Certificate via Ingress Annotation

This is the easiest method! You simply add a special label (called an 'annotation') to your website's routing configuration (called an 'Ingress'). CertManager reads this label and automatically creates and manages the certificate for you.

Step 1 — Create an Ingress with certificate annotation

Create a file called ingress.yaml. The key line is the annotation: cert-manager.io/cluster-issuer.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
namespace: default
annotations:
# This line tells CertManager to create a certificate automatically
cert-manager.io/cluster-issuer: custom-acme-issuer
cert-manager.io/duration: '2160h' # 90 days validity
cert-manager.io/renew-before: '720h' # Renew 30 days early
kubernetes.io/ingress.class: nginx # Your ingress controller
spec:
tls:
- hosts:
- example.com
- www.example.com
secretName: example-ingress-tls # CertManager creates this for you
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80

Step 2 — Apply the Ingress

kubectl apply -f ingress.yaml

Step 3 — What happens automatically

Once you apply this file, CertManager will automatically do all of the following for you:

  1. Create a Certificate resource behind the scenes.
  2. Verify that you own the domain using a DNS challenge.
  3. Request the certificate from the ACME server (e.g. FreeSSL).
  4. Store the certificate safely in a Kubernetes secret.
  5. Make the certificate available to your Ingress controller automatically.

Step 4 — Verify everything worked

# Check that CertManager created a certificate
kubectl get certificate

# Check that the secret was created
kubectl get secret example-ingress-tls

Section 2 — Certificate Renewal

One of the biggest advantages of CertManager is that certificates are renewed automatically. You do not need to do anything. However, this section explains how renewal works and how to force a renewal if you ever need to.

How Automatic Renewal Works

CertManager watches all your certificates 24/7. When a certificate is 30 days away from expiry, CertManager automatically starts the renewal process. The process is identical to the original issuance — it happens in the background without any downtime.

???? Default Renewal Timeline

Certificate valid for: 90 days

Renewal starts: 30 days before expiry

Your action required: NONE — fully automatic!

How to Monitor Automatic Renewal

# Watch all certificates across the cluster
kubectl get certificate -A -w

# Check details and history of a specific certificate
kubectl describe certificate example-com-cert

# Read CertManager's renewal log messages
kubectl logs -n cert-manager -l app=cert-manager -f | grep 'renewal'

How to Force a Manual Renewal

Sometimes you may need to renew a certificate right now — for example, if it was compromised or you updated your domain names. There are three ways to do this:

Method A — Delete the certificate secret (simplest)

When you delete the secret, CertManager detects it is missing and immediately creates a new one:

kubectl delete secret example-com-tls

???? Note

CertManager will recreate the secret automatically within a few seconds. Your service may briefly use no certificate during this short window.

Method B — Add a renewal annotation

This tells CertManager to issue a temporary certificate while the real one renews:

kubectl annotate certificate example-com-cert \
cert-manager.io/issue-temporary-certificate='true' --overwrite

Method C — Delete and re-create the certificate resource

This is the most thorough approach — it completely resets the certificate:

kubectl delete certificate example-com-cert
kubectl apply -f certificate.yaml

Custom Renewal Window (Advanced)

If 30 days is not long enough for your needs, you can customise when renewal starts. The example below renews 60 days before expiry instead:

spec:
duration: 2160h # 90-day certificate
renewBefore: 1440h # Start renewing 60 days before expiry

Section 3 — Advanced Configuration

Fixing DNS Propagation Delays (Extended Timeout)

Sometimes DNS changes take a long time to spread across the internet. If CertManager times out while waiting for DNS, you can increase the wait time.

Step 1 — Create a Certificate with custom DNS settings

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: long-propagation-cert
namespace: default
annotations:
cert-manager.io/issue-temporary-certificate: 'false'
spec:
secretName: long-propagation-tls
duration: 2160h
renewBefore: 720h
issuerRef:
name: custom-acme-issuer
kind: ClusterIssuer
dnsNames:
- slow-dns.example.com

Step 2 — Update CertManager controller timeout settings

Open the CertManager deployment for editing:

kubectl edit deployment cert-manager -n cert-manager

Find the 'args' section inside the container spec and add these lines:

args:
- --v=2
- --dns01-check-retry-period=60s # Check DNS every 60s (default was 10s)
- --max-concurrent-challenges=10 # Process 10 challenges at the same time
- --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53 # Use Google & Cloudflare DNS
- --dns01-recursive-nameservers-only=true

Step 3 — Restart CertManager to apply changes

kubectl rollout restart deployment cert-manager -n cert-manager
kubectl rollout status deployment cert-manager -n cert-manager

Using Multiple DNS Providers in One Setup

If your domains are spread across different DNS providers (e.g. some on Cloudflare, some on AWS Route53), you can configure a single ClusterIssuer to handle them all automatically using 'solvers'.

Example — Cloudflare for .com and Route53 for .net

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: multi-provider-issuer
spec:
acme:
server: https://www.freessl.in/acme-new/acme/directory
email: admin@example.com
privateKeySecretRef:
name: custom-acme-account-key
externalAccountBinding:
keyID: YOUR_EAB_KEY_ID
keySecretRef:
name: acme-eab-credentials
key: eab-hmac-key
keyAlgorithm: HS256
solvers:
# Use Cloudflare for .com domains
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- 'example.com'
- '*.example.com'
# Use AWS Route53 for .net domains
- dns01:
route53:
region: us-east-1
accessKeyID: AKIAXXXXXXXXXXXXXXXX
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-key
selector:
dnsZones:
- 'example.net'
- '*.example.net'

Section 4 — Cloudflare DNS Integration

CertManager uses Cloudflare to prove you own your domain by creating and deleting special DNS records automatically. This section explains how to set it up securely.

Step 1 — Create a Cloudflare API Token

For security, always create a token with the minimum permissions needed. Follow these steps in Cloudflare:

  1. Log in to your Cloudflare dashboard at https://dash.cloudflare.com
  2. Click on your profile icon (top right) and choose My Profile.
  3. Go to the API Tokens tab.
  4. Click Create Token.
  5. Select the Edit zone DNS template.
  6. Configure the token with these settings:
SettingValue
PermissionsZone ? DNS ? Edit
Zone ResourcesInclude ? Specific zone ? example.com
Client IP Filtering(Optional) Add your Kubernetes cluster IP addresses
TTL(Optional) Set an expiration date for the token
  1. Click Continue to summary, then Create Token.
  2. Copy the token — you will only see it once!

Step 2 — Store the Cloudflare Token in Kubernetes

Never put your API token directly in a YAML file. Store it as a Kubernetes secret:

kubectl create secret generic cloudflare-api-token-scoped \
--namespace=cert-manager \
--from-literal=api-token='YOUR_SCOPED_TOKEN_HERE'

Step 3 — Handling Multiple Cloudflare Accounts

If you manage multiple domains on different Cloudflare accounts, create a separate secret for each:

# Secret for first domain/account
kubectl create secret generic cloudflare-zone1 \
--namespace=cert-manager \
--from-literal=api-token='TOKEN_FOR_ZONE1'

# Secret for second domain/account
kubectl create secret generic cloudflare-zone2 \
--namespace=cert-manager \
--from-literal=api-token='TOKEN_FOR_ZONE2'

Configure ClusterIssuer to use zone-specific tokens

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cloudflare-multi-zone
spec:
acme:
server: https://www.freessl.in/acme-new/acme/directory
email: admin@example.com
privateKeySecretRef:
name: custom-acme-account-key
externalAccountBinding:
keyID: YOUR_EAB_KEY_ID
keySecretRef:
name: acme-eab-credentials
key: eab-hmac-key
keyAlgorithm: HS256
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-zone1
key: api-token
selector:
dnsZones:
- 'domain1.com'
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-zone2
key: api-token
selector:
dnsZones:
- 'domain2.com'

? About Cloudflare Proxy (Orange Cloud)

Good news: You do NOT need to change any Cloudflare proxy settings.

CertManager automatically creates a special TXT record called _acme-challenge.example.com.

This record is always created as DNS-only (grey cloud), regardless of your other settings.

Once domain ownership is verified, CertManager deletes the TXT record automatically.


Section 5 — Production Deployment Patterns

These are recommended, real-world patterns used in production environments. Choose the pattern that matches your infrastructure needs.

Pattern 1 — Namespace-Scoped Issuer (Multi-Tenant Setup)

Use this pattern when multiple teams share the same Kubernetes cluster and each team needs their own isolated certificate issuer.

???? When to use this

You have multiple tenants (teams, customers) in one cluster and want each team to have separate API keys, separate DNS access, and separate billing.

# Issuer lives in tenant-a namespace (not cluster-wide)
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: tenant-a-issuer
namespace: tenant-a
spec:
acme:
server: https://www.freessl.in/acme-new/acme/directory
email: tenant-a@example.com
privateKeySecretRef:
name: tenant-a-acme-key
externalAccountBinding:
keyID: TENANT_A_EAB_KEY
keySecretRef:
name: tenant-a-eab
key: eab-hmac-key
keyAlgorithm: HS256
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: tenant-a-cf-token
key: api-token
---
# Certificate for tenant A, using their scoped Issuer
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: tenant-a-cert
namespace: tenant-a
spec:
secretName: tenant-a-tls
issuerRef:
name: tenant-a-issuer
kind: Issuer # 'Issuer' not 'ClusterIssuer'
dnsNames:
- tenant-a.example.com

Pattern 2 — Certificate Per Ingress (Simplest for Apps)

This is the recommended pattern for individual applications. Each app gets its own certificate, and the Ingress controller handles everything automatically.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app1-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: custom-acme-issuer
nginx.ingress.kubernetes.io/force-ssl-redirect: 'true' # Always use HTTPS
spec:
tls:
- hosts:
- app1.example.com
secretName: app1-tls
rules:
- host: app1.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app1-service
port:
number: 80

Pattern 3 — Shared Wildcard Certificate Across Namespaces

Instead of creating a certificate for each app, create one wildcard certificate and share it across all your namespaces. This saves on certificate requests and simplifies management.

Step 1 — Create the wildcard certificate

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-shared
namespace: cert-manager
spec:
secretName: wildcard-shared-tls
duration: 2160h
renewBefore: 720h
issuerRef:
name: custom-acme-issuer
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.com

Step 2 — Install Reflector to share the secret across namespaces

Reflector is a small helper tool that automatically copies secrets to other namespaces. Install it with Helm:

helm repo add emberstack https://emberstack.github.io/helm-charts
helm install reflector emberstack/reflector --namespace kube-system

Step 3 — Allow the secret to be shared

Tell Reflector which namespaces are allowed to use this certificate:

kubectl annotate secret wildcard-shared-tls \
-n cert-manager \
reflector.v1.k8s.emberstack.com/reflection-allowed='true' \
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces='prod,staging,dev'

Step 4 — Use the shared certificate in any namespace

Once Reflector copies the secret, any namespace can use it in an Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: prod
spec:
tls:
- hosts:
- app.example.com
secretName: wildcard-shared-tls # Auto-replicated by Reflector
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80

Section 6 — Monitoring & Alerts

It is important to know when a certificate is about to expire, especially in case the automatic renewal fails for any reason. This section shows you how to set up monitoring.

Quick Status Check Commands

Run these commands anytime to see the health of all your certificates:

# List all certificates across all namespaces
kubectl get certificate -A

# Show certificates sorted by expiry date (closest first)
kubectl get certificate -A -o json | \
jq -r '.items[] | select(.status.notAfter != null) |
[.metadata.namespace, .metadata.name, .status.notAfter] | @tsv' | \
sort -k3

Prometheus Metrics (for Teams with Monitoring)

CertManager exposes its own metrics that Prometheus can scrape. This lets you build dashboards and automated alerts.

Where to find the metrics

ComponentMetrics URL
CertManager Controllerhttp://cert-manager:9402/metrics
CertManager Webhookhttp://cert-manager-webhook:9402/metrics

Key metrics to watch

Metric NameWhat It Tells You
certmanager_certificate_expiration_timestamp_secondsWhen each certificate expires
certmanager_certificate_ready_statusWhether each certificate is healthy (1 = good, 0 = problem)
certmanager_http_acme_challenge_totalHow many ACME challenges have been processed
certmanager_controller_sync_call_countHow active the controller is

Step 1 — Set up ServiceMonitor for Prometheus Operator

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: cert-manager
namespace: cert-manager
spec:
selector:
matchLabels:
app: cert-manager
endpoints:
- port: tcp-prometheus-servicemonitor
interval: 60s

Step 2 — Create Alert Rules

These alert rules will send a notification if a certificate is expiring soon or is not working:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cert-manager-alerts
namespace: cert-manager
spec:
groups:
- name: cert-manager
interval: 60s
rules:
# WARNING: Certificate expiring within 21 days
- alert: CertificateExpiryWarning
expr: (certmanager_certificate_expiration_timestamp_seconds - time()) < (21 * 24 * 3600)
for: 24h
labels:
severity: warning
annotations:
summary: 'Certificate {{ $labels.name }} expiring soon'
description: 'Certificate will expire in {{ $value | humanizeDuration }}'

# CRITICAL: Certificate not working for 10+ minutes
- alert: CertificateNotReady
expr: certmanager_certificate_ready_status == 0
for: 10m
labels:
severity: critical
annotations:
summary: 'Certificate {{ $labels.name }} not ready'
description: 'Certificate has been not ready for 10 minutes'

Section 7 — Troubleshooting

This section covers the most common problems and how to fix them. If a certificate is not working, follow the debugging steps in order.

How to Debug Any Certificate Problem

Step 1 — Check the certificate status

kubectl describe certificate example-com-cert

Look for the 'Events' section at the bottom. It will usually tell you exactly what went wrong.

Step 2 — Check the Order resource

An 'Order' is CertManager's internal request to the ACME server:

# List all orders
kubectl get orders -A

# See details of a specific order
kubectl get order -n default
kubectl describe order <order-name> -n default

Step 3 — Check the Challenge status

A 'Challenge' is CertManager's attempt to prove you own the domain:

# List all challenges
kubectl get challenges -A

# See details of a specific challenge
kubectl describe challenge <challenge-name> -n default
Challenge StatusWhat It Means
pendingChallenge created, waiting for DNS record to appear
processingDNS record found, waiting for ACME server to validate
validChallenge passed — certificate is being issued
invalidChalle

Have any Questions

Call HTTPS

If you have any questions, feel free to call us

© 2025 https.in. All rights reserved.