September 4, 2026
Red Hat Cert Manager: Certificates with Letβs Encrypt
This runbook shows how to use cert-manager and Letβs Encrypt to manage TLS certificates for an OpenShift cluster.

By Fabio Reis
5 min read
The main endpoints are:
API: api.ocp.example.com
OAuth: oauth-openshift.apps.ocp.example.com
Console: console-openshift-console.apps.ocp.example.com
Apps: *.apps.ocp.example.comAPI: api.ocp.example.com
OAuth: oauth-openshift.apps.ocp.example.com
Console: console-openshift-console.apps.ocp.example.com
Apps: *.apps.ocp.example.comLet's Encrypt issues publicly trusted certificates. There is no Custom CA to create, distribute, or configure in the OpenShift trust bundle.
The only requirement is that cert-manager can create DNS TXT records in the public DNS zone.
For Custom CA try this medium post :
Red Hat Cert-Manager with Custom CA In this lab, I want OpenShift to use certificates signed by my own Custom CA.
Why DNS-01?
DNS-01 validation is used in this lab because:
- Wildcard certificates require DNS-01.
- The API uses port
6443, not standard HTTPS port443. - Validation does not require exposing an HTTP endpoint to Let's Encrypt.
- The API can remain private while DNS remains publicly authoritative.
The flow is:
cert-manager
β
DNS provider creates _acme-challenge TXT records
β
Letβs Encrypt validates domain ownership
β
cert-manager creates or renews TLS Secrets
β
OpenShift API Server and Ingress Controller consume the Secretscert-manager
β
DNS provider creates _acme-challenge TXT records
β
Letβs Encrypt validates domain ownership
β
cert-manager creates or renews TLS Secrets
β
OpenShift API Server and Ingress Controller consume the SecretsPrerequisites
Before starting, make sure you have:
- Cluster administrator access.
- The Red Hat build of cert-manager installed.
- A public DNS zone for
ocp.example.com. - A DNS provider supported by cert-manager.
- An API token that allows cert-manager to edit DNS TXT records.
ocand OpenSSL installed on your workstation.
Check cert-manager:
oc get pods -n cert-manager
oc get crd | grep cert-manager.iooc get pods -n cert-manager
oc get crd | grep cert-manager.ioThis example uses Cloudflare as the DNS provider. The same design works with Route 53, Azure DNS, Google Cloud DNS, and other supported DNS providers.
1. Create the DNS API token Secret
Create a Cloudflare API token with these permissions:
Zone / DNS / Edit
Zone / Zone / ReadZone / DNS / Edit
Zone / Zone / ReadRestrict the token to the DNS zone that contains ocp.example.com.
Create the Secret in the cert-manager namespace:
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: <YOUR-CLOUDFLARE-API-TOKEN>apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: <YOUR-CLOUDFLARE-API-TOKEN>Apply it:
oc apply -f cloudflare-api-token-secret.yamloc apply -f cloudflare-api-token-secret.yamlDo not commit the API token to Git. In a GitOps workflow, use External Secrets, Sealed Secrets, or SOPS.
2. Create a Let's Encrypt ClusterIssuer
Start with the staging endpoint while testing. It prevents production rate-limit issues.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: your-email@example.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
selector:
dnsZones:
- "ocp.example.com"apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: your-email@example.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
selector:
dnsZones:
- "ocp.example.com"Apply and validate it:
oc apply -f letsencrypt-staging-issuer.yaml
oc get clusterissuer letsencrypt-stagingoc apply -f letsencrypt-staging-issuer.yaml
oc get clusterissuer letsencrypt-stagingAfter validation, create the production issuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: your-email@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
selector:
dnsZones:
- "ocp.example.com"
oc apply -f letsencrypt-prod-issuer.yaml
oc get clusterissuer letsencrypt-prodapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: your-email@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
selector:
dnsZones:
- "ocp.example.com"
oc apply -f letsencrypt-prod-issuer.yaml
oc get clusterissuer letsencrypt-prodExpected result:
NAME READY AGE
letsencrypt-prod True 1mNAME READY AGE
letsencrypt-prod True 1m3. Create the API certificate
The API Server expects its custom serving certificate Secret in openshift-config.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-letsencrypt-cert
namespace: openshift-config
spec:
secretName: api-letsencrypt-cert
renewBefore: 360h
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- api.ocp.example.comapiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-letsencrypt-cert
namespace: openshift-config
spec:
secretName: api-letsencrypt-cert
renewBefore: 360h
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- api.ocp.example.comApply it:
oc apply -f api-letsencrypt-cert.yamloc apply -f api-letsencrypt-cert.yamlMonitor the request:
oc get certificate api-letsencrypt-cert \
-n openshift-config -woc get certificate api-letsencrypt-cert \
-n openshift-config -wDuring validation, cert-manager creates an ACME Order and Challenge:
oc get order -n openshift-config
oc get challenge -n openshift-configoc get order -n openshift-config
oc get challenge -n openshift-config4. Configure the API Server
First, check whether named API certificates already exist:
oc get apiserver cluster \
-o jsonpath='{.spec.servingCerts.namedCertificates}{"\n"}'oc get apiserver cluster \
-o jsonpath='{.spec.servingCerts.namedCertificates}{"\n"}'For a lab without existing named certificates, apply this patch:
oc patch apiserver cluster \
--type=merge \
-p '{
"spec": {
"servingCerts": {
"namedCertificates": [
{
"names": [
"api.ocp.example.com"
],
"servingCertificate": {
"name": "api-letsencrypt-cert"
}
}
]
}
}
}'oc patch apiserver cluster \
--type=merge \
-p '{
"spec": {
"servingCerts": {
"namedCertificates": [
{
"names": [
"api.ocp.example.com"
],
"servingCertificate": {
"name": "api-letsencrypt-cert"
}
}
]
}
}
}'This patch replaces the complete
namedCertificateslist. If entries already exist, useoc edit apiserver clusterand append the new certificate.
Check the API Server Operator:
oc get co kube-apiserveroc get co kube-apiserverValidate the API certificate:
openssl s_client \
-connect api.ocp.example.com:6443 \
-servername api.ocp.example.com </dev/nullopenssl s_client \
-connect api.ocp.example.com:6443 \
-servername api.ocp.example.com </dev/nullThe certificate chain should be trusted without providing a custom CA file.
5. Create the wildcard certificate for Apps
The wildcard certificate covers every default application Route, including OAuth and the standard Console route.
Create the Certificate in openshift-ingress:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: apps-letsencrypt-wildcard
namespace: openshift-ingress
spec:
secretName: apps-letsencrypt-wildcard
renewBefore: 360h
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- "*.apps.ocp.example.com"apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: apps-letsencrypt-wildcard
namespace: openshift-ingress
spec:
secretName: apps-letsencrypt-wildcard
renewBefore: 360h
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- "*.apps.ocp.example.com"Apply it:
oc apply -f apps-letsencrypt-wildcard.yamloc apply -f apps-letsencrypt-wildcard.yamlWait until it is ready:
oc get certificate apps-letsencrypt-wildcard \
-n openshift-ingress -woc get certificate apps-letsencrypt-wildcard \
-n openshift-ingress -wCheck the DNS-01 resources if the certificate does not become ready:
oc get order -n openshift-ingress
oc get challenge -n openshift-ingressoc get order -n openshift-ingress
oc get challenge -n openshift-ingress6. Configure the default Ingress Controller
Configure only the default certificate field, preserving the existing Ingress Controller settings:
oc patch ingresscontroller default \
-n openshift-ingress-operator \
--type=merge \
-p '{"spec":{"defaultCertificate":{"name":"apps-letsencrypt-wildcard"}}}'oc patch ingresscontroller default \
-n openshift-ingress-operator \
--type=merge \
-p '{"spec":{"defaultCertificate":{"name":"apps-letsencrypt-wildcard"}}}'Confirm the configured Secret:
oc get ingresscontroller default \
-n openshift-ingress-operator \
-o jsonpath='{.spec.defaultCertificate.name}{"\n"}'oc get ingresscontroller default \
-n openshift-ingress-operator \
-o jsonpath='{.spec.defaultCertificate.name}{"\n"}'Expected result:
apps-letsencrypt-wildcardapps-letsencrypt-wildcardThe Ingress Controller automatically observes Secret updates. No router restart is required.
7. Validate OAuth, Console, and application Routes
Check the OAuth Route hostname:
oc get route oauth-openshift \
-n openshift-authentication \
-o jsonpath='{.spec.host}{"\n"}'oc get route oauth-openshift \
-n openshift-authentication \
-o jsonpath='{.spec.host}{"\n"}'Validate OAuth:
openssl s_client \
-connect oauth-openshift.apps.ocp.example.com:443 \
-servername oauth-openshift.apps.ocp.example.com </dev/nullopenssl s_client \
-connect oauth-openshift.apps.ocp.example.com:443 \
-servername oauth-openshift.apps.ocp.example.com </dev/nullValidate the Console:
openssl s_client \
-connect console-openshift-console.apps.ocp.example.com:443 \
-servername console-openshift-console.apps.ocp.example.com </dev/nullopenssl s_client \
-connect console-openshift-console.apps.ocp.example.com:443 \
-servername console-openshift-console.apps.ocp.example.com </dev/nullValidate an application Route:
openssl s_client \
-connect myapp.apps.ocp.example.com:443 \
-servername myapp.apps.ocp.example.com </dev/nullopenssl s_client \
-connect myapp.apps.ocp.example.com:443 \
-servername myapp.apps.ocp.example.com </dev/nullAll endpoints should return:
Verify return code: 0 (ok)Verify return code: 0 (ok)The default Console hostname is already covered by *.apps.ocp.example.com. No separate Console certificate or Console resource patch is required.
8. Log in with oc
Because Let's Encrypt is publicly trusted, no --certificate-authority argument is required:
oc login \
--server=https://api.ocp.example.com:6443 \
-u kubeadminoc login \
--server=https://api.ocp.example.com:6443 \
-u kubeadminDuring login, oc redirects authentication to:
oauth-openshift.apps.ocp.example.comoauth-openshift.apps.ocp.example.comThis is why the API certificate and the application wildcard certificate must both be correctly configured.
Troubleshooting
The Certificate remains READY=False
Check the Certificate, Order, and Challenge resources:
oc describe certificate api-letsencrypt-cert \
-n openshift-config
oc get order -n openshift-config
oc get challenge -n openshift-configoc describe certificate api-letsencrypt-cert \
-n openshift-config
oc get order -n openshift-config
oc get challenge -n openshift-configFor the wildcard certificate:
oc describe certificate apps-letsencrypt-wildcard \
-n openshift-ingress
oc get order -n openshift-ingress
oc get challenge -n openshift-ingressoc describe certificate apps-letsencrypt-wildcard \
-n openshift-ingress
oc get order -n openshift-ingress
oc get challenge -n openshift-ingressThe usual causes are:
- Invalid DNS provider API token.
- Missing permissions to edit TXT records.
- The wrong DNS zone in
dnsZones. - DNS propagation delay.
- Let's Encrypt rate limits.
The API certificate works, but oc login fails
Validate the API certificate first:
openssl s_client \
-connect api.ocp.example.com:6443 \
-servername api.ocp.example.com </dev/nullopenssl s_client \
-connect api.ocp.example.com:6443 \
-servername api.ocp.example.com </dev/nullThen validate the OAuth Route:
openssl s_client \
-connect oauth-openshift.apps.ocp.example.com:443 \
-servername oauth-openshift.apps.ocp.example.com </dev/nullopenssl s_client \
-connect oauth-openshift.apps.ocp.example.com:443 \
-servername oauth-openshift.apps.ocp.example.com </dev/nullIf OAuth still presents an issuer similar to this:
issuer=CN=ingress-operator@...issuer=CN=ingress-operator@...the default Ingress Controller is still using the OpenShift-generated certificate.
Confirm the configured Secret:
oc get ingresscontroller default \
-n openshift-ingress-operator \
-o jsonpath='{.spec.defaultCertificate.name}{"\n"}'oc get ingresscontroller default \
-n openshift-ingress-operator \
-o jsonpath='{.spec.defaultCertificate.name}{"\n"}'It must return:
apps-letsencrypt-wildcardapps-letsencrypt-wildcardThe Authentication Operator becomes degraded
Check its status:
oc get co authenticationoc get co authenticationIf the error contains RouterCertsDegraded, inspect the certificate chain stored in the wildcard Secret:
oc -n openshift-ingress extract secret/apps-letsencrypt-wildcard \
--to=/tmp/apps-letsencrypt-cert
openssl crl2pkcs7 -nocrl \
-certfile /tmp/apps-letsencrypt-cert/tls.crt \
| openssl pkcs7 -print_certs -nooutoc -n openshift-ingress extract secret/apps-letsencrypt-wildcard \
--to=/tmp/apps-letsencrypt-cert
openssl crl2pkcs7 -nocrl \
-certfile /tmp/apps-letsencrypt-cert/tls.crt \
| openssl pkcs7 -print_certs -nooutThe output must contain the endpoint certificate and its intermediate CA certificate.
Do not add a Custom CA ConfigMap or configure Proxy.spec.trustedCA for Let's Encrypt. Let's Encrypt is already trusted by OpenShift and standard client trust stores.
Final result
cert-manager now manages the lifecycle of two Let's Encrypt certificates:
Letβs Encrypt
β
DNS-01 validation
β
cert-manager ClusterIssuer
β
TLS Secrets
βββ openshift-config / api-letsencrypt-cert
βββ openshift-ingress / apps-letsencrypt-wildcard
β
OpenShift API, OAuth, Console, and application RoutesLetβs Encrypt
β
DNS-01 validation
β
cert-manager ClusterIssuer
β
TLS Secrets
βββ openshift-config / api-letsencrypt-cert
βββ openshift-ingress / apps-letsencrypt-wildcard
β
OpenShift API, OAuth, Console, and application RoutesThe certificates renew automatically before expiration. The API, OAuth endpoint, Console, and application Routes use publicly trusted certificates, eliminating the need for a Custom CA on user workstations.