In this guide, we’ll walk through installing cert-manager using Helm on your Kubernetes cluster. We’ll also configure it to use specific DNS resolvers, which is helpful when working with split-horizon DNS or internal/external domains.
Prerequisites
- A running Kubernetes cluster (v1.25+)
- Helm installed (
helm version
) - Access to modify DNS records (e.g., Cloudflare)
- DNS domain ready (e.g.,
maksonlee.com
) - A Cloudflare API token stored in a Kubernetes Secret
- Add the cert-manager Helm Repository
helm repo add jetstack https://charts.jetstack.io --force-update
- Install cert-manager
We install cert-manager in its own namespace and configure it to create its required CRDs automatically:
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.17.1 \
--set crds.enabled=true \
--set 'extraArgs={--dns01-recursive-nameservers-only,--dns01-recursive-nameservers=8.8.8.8:53\,1.1.1.1:53}'
The extraArgs
section ensures cert-manager uses public recursive DNS servers to validate DNS-01 challenges, bypassing any internal DNS caching or split-horizon issues.
- Create Cloudflare API Token Secret
Create a Kubernetes Secret containing your Cloudflare API token (with permission to edit DNS records):
kubectl create secret generic cloudflare-api-secret \
--from-literal=api-token=<YOUR_CLOUDFLARE_API_TOKEN> \
--namespace cert-manager
- Create a ClusterIssuer (Staging)
We recommend testing with Let’s Encrypt staging first:
# clusterissuer-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: cdlee123@gmail.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-secret
key: api-token
Apply it:
kubectl apply -f clusterissuer-staging.yaml
- Create a Certificate
Here’s an example of issuing a certificate for thingsboard.maksonlee.com
:
# thingsboard-cert
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: thingsboard-cert
namespace: thingsboard
spec:
secretName: thingsboard-tls
commonName: thingsboard.maksonlee.com
dnsNames:
- thingsboard.maksonlee.com
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
Apply it:
kubectl apply -f thingsboard-cert
- Verification
Check certificate status:
kubectl describe certificate thingsboard-cert -n thingsboard
A successful output should include:
Type: Ready
Status: True
Reason: Ready
Message: Certificate is up to date and has not expired
- Switch to Production
Once staging works, change your ClusterIssuer to the production endpoint:
# clusterissuer-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: cdlee123@gmail.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-secret
key: api-token
Apply it:
kubectl apply -f clusterissuer-prod.yaml
Then update your Certificate to use the production issuer:
kubectl edit certificate thingsboard-cert -n thingsboard
Change:
issuerRef:
name: letsencrypt-staging
to:
issuerRef:
name: letsencrypt-prod
cert-manager will detect the change, invalidate the previous certificate, and issue a new one from production.