Beware! This is still work in progress – I’ll need to re-run all steps on a clean install to verify everything really works as expected!
I also assume, that you’re familiar with DNS-based ACME and that the required infrastructure is already in place.
Installation of cert-manager
Based on this description.
linux # kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.1/cert-manager.yaml
namespace/cert-manager created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
serviceaccount/cert-manager-cainjector created
serviceaccount/cert-manager created
serviceaccount/cert-manager-webhook created
clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrole.rbac.authorization.k8s.io/cert-manager-cluster-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-edit created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests created
clusterrole.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews created
role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
role.rbac.authorization.k8s.io/cert-manager:leaderelection created
role.rbac.authorization.k8s.io/cert-manager-tokenrequest created
role.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created
rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager-tokenrequest created
rolebinding.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created
service/cert-manager-cainjector created
service/cert-manager created
service/cert-manager-webhook created
deployment.apps/cert-manager-cainjector created
deployment.apps/cert-manager created
deployment.apps/cert-manager-webhook created
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
Check installation:
linux # kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-684886b7df-bdjgd 1/1 Running 0 110s
cert-manager-cainjector-7b86575979-k52tk 1/1 Running 0 110s
cert-manager-webhook-7d8878ddb-z5ns2 1/1 Running 0 110s
linux # kubectl get services --namespace cert-manager
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cert-manager ClusterIP 10.43.61.185 <none> 9402/TCP 66s
cert-manager-cainjector ClusterIP 10.43.175.196 <none> 9402/TCP 66s
cert-manager-webhook ClusterIP 10.43.158.172 <none> 443/TCP,9402/TCP 66s
Creating first ACME config (using DNS-01/RFC2136 based protocol):
linux # cat letsencrypt-prod-rfc2136.yml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-rfc2136-prod-issuer
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-rfc2136-prod-issuer
solvers:
- dns01:
rfc2136:
nameserver: 192.168.1.1
tsigKeyName: update-external.
tsigAlgorithm: HMACSHA256
tsigSecretSecretRef:
name: letsencrypt-rfc2136-update-external
key: letsencrypt-rfc2136-update-external-key
Applying it:
linux # kubectl apply -f letsencrypt-prod-rfc2136.yml
clusterissuer.cert-manager.io/letsencrypt-rfc2136-prod-issuer created
Adding secret (make sure the namespace is “cert-manager“):
linux # kubectl -n cert-manager create secret generic letsencrypt-rfc2136-update-external --from-literal=letsencrypt-rfc2136-update-external-key=RandomKeyHere
secret/letsencrypt-rfc2136-update-external created
Now get a certificate for a specific service (in my case “whoami.mydomain.de“):
linux # cat whoami-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: whoami-mydomain
namespace: default
spec:
secretName: whoami-tls
issuerRef:
name: letsencrypt-rfc2136-prod-issuer
dnsNames:
- whoami.mydomain.de
linux # kubectl apply -f whoami-certificate.yaml
certificate.cert-manager.io/whoami-mydomain created
linux # kubectl describe certificate whoami-mydomain
Name: whoami-mydomain
Namespace: default
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Certificate
Metadata:
Creation Timestamp: 2026-04-07T15:07:51Z
Generation: 1
Resource Version: 35518
UID: 84878eb6-2b24-4317-8021-46c3115b04ea
Spec:
Dns Names:
whoami.mydomain.de
Issuer Ref:
Name: letsencrypt-rfc2136-prod-issuer
Secret Name: whoami-tls
Status:
Conditions:
Last Transition Time: 2026-04-07T15:07:51Z
Message: Issuing certificate as Secret does not exist
Observed Generation: 1
Reason: DoesNotExist
Status: False
Type: Ready
Events: <none>
FIXME: Add intermediate steps required to setup a fresh deployment/pod.
Connect it to (pre-existing) pod using ingress controller:
linux # cat whoami-ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-tls-ingress
annotations:
spec.ingressClassName: traefik
cert-manager.io/cluster-issuer: letsencrypt-rfc2136-prod-issuer
spec:
rules:
- host: whoami.mydomain.de
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
number: 5678
tls:
- secretName: whoami-tls
hosts:
- whoami.mydomain.de
linux # kubectl apply -f whoami-ingress-tls.yaml
ingress.networking.k8s.io/whoami-tls-ingress created
Cert-manager troubleshooting guide
Detailed instructions can be found here.
What helped me most was to check the different states of certificate creation:
certificatescertificaterequestsorderschallenges
linux # kubectl get certificates
linux # kubectl get certificaterequests
linux # kubectl get orders
linux # kubectl get challenges
If you require more details use “describe” instead of “get” (maybe also add the specific entry you want described):
linux # kubectl describe orders
<...>
Or take a look at the logs of cert-manager itself:
linux # kubectl logs -n cert-manager -lapp.kubernetes.io/name=cert-manager -f
Special case: split DNS
In case of split DNS (or other special DNS configurations) you may want to force using specific DNS servers for ACME.
You can edit the cert-manager options (--dns01-recursive-nameservers-only and --dns01-recursive-nameservers) by looking for spec/template/spec/containers/args:
linux # kubectl edit deployment cert-manager -n cert-manager
<...>
spec:
<...>
template:
<...>
spec:
containers:
args:
- --v=2
- --cluster-resource-namespace=$(POD_NAMESPACE)
- --leader-election-namespace=kube-system
- --acme-http01-solver-image=quay.io/jetstack/cert-manager-acmesolver:v1.20.1
- --max-concurrent-challenges=60
- --dns01-recursive-nameservers-only
- --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53
<...>
Install kubectl extension
See here:
linux # curl -L -o kubectl-cert-manager.tar.gz https://github.com/cert-manager/cert-manager/releases/download/v1.2.0/kubectl-cert_manager-linux-amd64.tar.gz
linux # tar xzf kubectl-cert-manager.tar.gz
linux # sudo mv kubectl-cert_manager /usr/local/bin
Run it like:
linux # kubectl cert-manager renew --all
Manually triggered issuance of Certificate default/whoami-tls
