This guide walks you through installing cert-manager — a tool that automatically handles SSL certificates for your websites running inside Kubernetes. Think of cert-manager as your personal certificate assistant: it requests, renews, and manages SSL certificates so your sites can use secure HTTPS connections without manual work.
In plain terms, this is what cert-manager does for you:
???? Who Should Use This Guide?
This guide is ideal for DevOps engineers, system administrators, and developers who manage Kubernetes clusters and want to automate SSL certificate management using a custom ACME server (such as FreeSSL).
| Detail | Value |
|---|---|
| Target Platform | Kubernetes (version 1.19 or higher) |
| DNS Provider Used | Cloudflare (can also use AWS, Google Cloud, DigitalOcean) |
| Validation Method | DNS-01 (proves domain ownership via DNS records) |
| ACME Server URL | https://www.freessl.in/acme-new/acme/directory |
| Certificate Renewal | Automatic — cert-manager handles it for you |
| Installation Method | Helm (recommended) or kubectl YAML manifests |
Before installing cert-manager, make sure you have the following ready.
Kubernetes is the system that runs and manages your applications inside containers. cert-manager is installed inside Kubernetes and requires version 1.19 or newer.
Check your Kubernetes version:
kubectl version --shortVerify cluster nodes are running:
kubectl get nodeskubectl is the command-line tool you use to talk to your Kubernetes cluster. It should already be installed and connected to your cluster. If you can run the commands above without errors, you are good to go.
Helm is a package manager for Kubernetes — similar to how App Store or apt-get works for apps, Helm helps you install Kubernetes applications easily. We recommend using Helm to install cert-manager.
You need admin-level access to your Kubernetes cluster. cert-manager creates cluster-wide resources that require elevated permissions.
cert-manager uses Cloudflare's API to automatically create temporary DNS records to prove that you own the domain before issuing a certificate. You will create a special API token from your Cloudflare account in a later step.
EAB stands for External Account Binding. These are special credentials (a Key ID and HMAC Key) that the custom ACME server (FreeSSL) requires to verify your account before issuing certificates. Obtain these from your ACME server administrator before you begin.
?? Important: You need your EAB Key ID and HMAC Key ready before you can complete the setup. Contact your ACME server administrator to get these values.
There are two ways to install cert-manager. We strongly recommend Method 1 (Helm) as it is easier to manage and upgrade.
Add the cert-manager Helm Repository
This tells Helm where to find the cert-manager package.
helm repo add jetstack https://charts.jetstack.io
helm repo update
The first command registers the official cert-manager repository. The second fetches the latest package list.
Create the cert-manager Namespace
A namespace is like a folder inside Kubernetes to keep cert-manager's components organized and separate from your other applications.
kubectl create namespace cert-managerInstall cert-manager
This installs cert-manager and all its required components (CRDs) into your cluster.
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.13.3 \
--set installCRDs=true \
--set global.leaderElection.namespace=cert-managerWhat these options mean:
--namespace cert-manager — Installs into the namespace you just created--version v1.13.3 — Installs a specific, tested version--set installCRDs=true — Also installs the Custom Resource DefinitionsVerify the Installation
Check that all three cert-manager components are running successfully.
kubectl get pods -n cert-managerYou should see three pods all showing STATUS: Running:
NAME READY STATUS RESTARTS AGE
cert-manager-7d9f8c8d4f-xxxxx 1/1 Running 0 2m
cert-manager-cainjector-5c5695c4b-xxxxx 1/1 Running 0 2m
cert-manager-webhook-6b8d9c7f5d-xxxxx 1/1 Running 0 2m? Success Check: If all three pods show 1/1 Running, your cert-manager installation is successful. If any pod shows Error or CrashLoopBackOff, wait a minute and check again. If the problem persists, check Part 8 (Troubleshooting).
If you do not have Helm installed, you can install cert-manager directly using kubectl with a single command:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
After installation, verify the Custom Resource Definitions (CRDs) were installed correctly:
kubectl get crd | grep cert-managerYou should see six entries listed:
certificaterequests.cert-manager.io
certificates.cert-manager.io
challenges.acme.cert-manager.io
clusterissuers.cert-manager.io
issuers.cert-manager.io
orders.acme.cert-manager.iocert-manager needs access to your DNS provider (Cloudflare) to prove that you own your domain. It does this by automatically creating a temporary DNS record during certificate issuance — this is called the DNS-01 challenge.
An API Token gives cert-manager limited access to your Cloudflare account — only enough to create and delete DNS records.
?? Security Note: Treat your API token like a password. Do not share it or store it in plain text. It will be stored securely inside Kubernetes as a Secret in the next step.
Run this command, replacing YOUR_CLOUDFLARE_API_TOKEN_HERE with your actual token:
kubectl create secret generic cloudflare-api-token \
--namespace=cert-manager \
--from-literal=api-token='YOUR_CLOUDFLARE_API_TOKEN_HERE'Verify the secret was created:
kubectl get secret cloudflare-api-token -n cert-managerAlternatively, using a YAML file:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: YOUR_CLOUDFLARE_API_TOKEN_HERE
EOFEAB (External Account Binding) credentials are required by your custom ACME server (FreeSSL) to verify that you have a valid account. You should have received two values from your administrator:
kubectl create secret generic acme-eab-credentials \
--namespace=cert-manager \
--from-literal=eab-kid='YOUR_EAB_KEY_ID_HERE' \
--from-literal=eab-hmac-key='YOUR_HMAC_KEY_HERE'Verify the secret was created:
kubectl get secret acme-eab-credentials -n cert-manager -o yamlAlternatively, using a YAML file:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: acme-eab-credentials
namespace: cert-manager
type: Opaque
stringData:
eab-kid: YOUR_EAB_KEY_ID_HERE
eab-hmac-key: YOUR_HMAC_KEY_HERE
EOFA ClusterIssuer is a Kubernetes resource that tells cert-manager exactly how to issue certificates. Think of it as a profile or template that cert-manager uses every time it needs to request a certificate.
Create a new file named cluster-issuer.yaml and paste the following content, replacing placeholders with your actual values:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: custom-acme-issuer
spec:
acme:
# The address of your ACME server
server: https://www.freessl.in/acme-new/acme/directory
# Your email --- used for expiry notifications
email: admin@example.com
# Where to store the ACME account private key
privateKeySecretRef:
name: custom-acme-account-key
# EAB credentials for account verification
externalAccountBinding:
keyID: YOUR_EAB_KEY_ID_HERE
keySecretRef:
name: acme-eab-credentials
key: eab-hmac-key
keyAlgorithm: HS256
# DNS validation using Cloudflare
solvers:
- dns01:
cloudflare:
email: cloudflare@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- "example.com"
- "*.example.com"
Replace these values:
admin@example.com — your email for notificationsYOUR_EAB_KEY_ID_HERE — your EAB Key ID from Part 3cloudflare@example.com — your Cloudflare account emailexample.com — your actual domain namekubectl apply -f cluster-issuer.yamlkubectl get clusterissuer custom-acme-issuer -o wideA successful result looks like this:
NAME READY STATUS AGE
custom-acme-issuer True Account registered 1mThe READY column must show True. If it shows False, check the detailed status:
kubectl describe clusterissuer custom-acme-issuerIf you experience timeouts or slow certificate issuance, you can tune these settings.
Create a file called values-custom.yaml with the following content:
global:
logLevel: 2 # 0 = minimal, 6 = very detailed
controller:
extraArgs:
- --dns01-check-retry-period=30s
- --max-concurrent-challenges=20
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
webhook:
timeoutSeconds: 30Apply with:
helm upgrade cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.13.3 \
--values values-custom.yamlkubectl edit deployment cert-manager -n cert-managerIn the editor, find the args section under containers and add:
args:
- --dns01-check-retry-period=30s
- --max-concurrent-challenges=20
- --v=2If you use a different DNS provider, here are the configurations for the most common alternatives.
apiVersion: v1
kind: Secret
metadata:
name: route53-credentials
namespace: cert-manager
type: Opaque
stringData:
secret-access-key: YOUR_AWS_SECRET_KEYapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: custom-acme-route53
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
keySecretRef:
name: acme-eab-credentials
key: eab-hmac-key
keyAlgorithm: HS256
solvers:
- dns01:
route53:
region: us-east-1
accessKeyID: YOUR_AWS_ACCESS_KEY_ID
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-key
kubectl create secret generic clouddns-sa \
--namespace=cert-manager \
--from-file=key.json=gcp-service-account.jsonapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: custom-acme-clouddns
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
keySecretRef:
name: acme-eab-credentials
key: eab-hmac-key
keyAlgorithm: HS256
solvers:
- dns01:
cloudDNS:
project: your-gcp-project-id
serviceAccountSecretRef:
name: clouddns-sa
key: key.json
apiVersion: v1
kind: Secret
metadata:
name: digitalocean-dns
namespace: cert-manager
type: Opaque
stringData:
access-token: YOUR_DIGITALOCEAN_API_TOKEN
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: custom-acme-digitalocean
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
keySecretRef:
name: acme-eab-credentials
key: eab-hmac-key
keyAlgorithm: HS256
solvers:
- dns01:
digitalocean:
tokenSecretRef:
name: digitalocean-dns
key: access-token
# List all ClusterIssuers and their status
kubectl get clusterissuer
# See detailed information about your ClusterIssuer
kubectl describe clusterissuer custom-acme-issuer
# View live cert-manager logs
kubectl logs -n cert-manager -l app=cert-manager -f
# View webhook logs
kubectl logs -n cert-manager -l app=webhook -f
Replace test.example.com with a domain you actually control:
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: test-certificate
namespace: default
spec:
secretName: test-certificate-tls
issuerRef:
name: custom-acme-issuer
kind: ClusterIssuer
dnsNames:
- test.example.com
EOFkubectl get certificate test-certificate -wkubectl describe certificate test-certificatekubectl get secret test-certificate-tls -o yamlThis usually means the EAB credentials are incorrect, or cert-manager cannot reach the ACME server.
# Check for error messages
kubectl describe clusterissuer custom-acme-issuer
# Check cert-manager logs for errors
kubectl logs -n cert-manager -l app=cert-manager --tail=100
# Verify your EAB Key ID is correct
kubectl get secret acme-eab-credentials -n cert-manager -o jsonpath='{.data.eab-kid}' | base64 -dCommon fixes:
If your DNS provider is slow, cert-manager may time out before the record is visible. Increase the DNS check interval:
kubectl edit deployment cert-manager -n cert-manager
# In the editor, add or change this argument:
# --dns01-check-retry-period=60sFirst, test if the ACME server is reachable:
curl -k https://www.freessl.in/acme-new/acme/directory