Skip to content

Certificate Management with cert-manager ​

Last Updated: October 12, 2026 Validation baseline: cert-manager 1.21.2, cmctl 2.5.0, trust-manager 0.25.0, istio-csr 0.17.0, aws-privateca-issuer 1.9.2, ACK ACM 1.8.1. cert-manager 1.21's published supported/tested Kubernetes range is 1.33–1.36; Kubernetes 1.37 is outside that range, so this guide does not assert compatibility with it.

cert-manager manages certificate issuance and renewal as Kubernetes resources. CA trust distribution, application reload, revocation, and CRL/OCSP operations remain separate responsibilities. Examples were checked with local schemas, configuration, and libraries; they are not evidence of external CA issuance or AWS/Kubernetes deployment. A separate ephemeral local Vault 2.1.0 test used a synthetic CA for sixteen issuance/CSR-signing acceptance/rejection cases.

Overview ​

cert-manager joined CNCF on November 10, 2020, became Incubating on September 19, 2022, and Graduated on September 29, 2024. Graduation does not guarantee the security or availability of a particular deployment.

Lifecycle concernResponsibility
IssuanceIssuer authentication, requester approval, SAN/usage/lifetime policy
RenewalActual issued lifetime, ARI/renewBefore, retries and alerts
Key rotationSecret access, consumer reload, CA rollover order
TrustWhich roots/intermediates each namespace/process trusts
RevocationCA revocation procedure and CRL/OCSP publication/consumption

Do not use the old 1.16.2 installation and compatibility table as current support guidance. Version 1.16 reached EOL in June 2025. Plan upgrades using intermediate release notes and CRD changes.

Architecture ​

cert-manager components and issuer/Secret relationships

cert-manager components and issuer/Secret relationshipsOpen full screen ↗

The controller reconciles Certificates and creates CertificateRequests; issuer controllers handle issuance. The webhook validates/defaults/converts custom resources, and cainjector manages CA bundles in supported API/webhook configurations. CA/SelfSigned issuers are not necessarily external services.

Installation ​

Check supported Kubernetes versions, Helm/cluster permissions, Gateway API CRDs and a real Gateway controller, and optional Prometheus Operator CRDs. If Gateway API CRDs are installed after controller startup, verify restart/discovery requirements.

bash
helm repo add jetstack https://charts.jetstack.io
helm repo update jetstack
helm upgrade --install cert-manager jetstack/cert-manager \
  --version v1.21.2 --namespace cert-manager --create-namespace \
  --values cert-manager-values.yaml
kubectl get pods -n cert-manager
cmctl check api
yaml
crds:
  enabled: true
  keep: true
replicaCount: 2
podDisruptionBudget:
  enabled: true
  minAvailable: 1
config:
  apiVersion: controller.config.cert-manager.io/v1alpha1
  kind: ControllerConfiguration
  gatewayAPI:
    enabled: true
prometheus:
  enabled: true
  servicemonitor:
    enabled: true
webhook:
  replicaCount: 2
  timeoutSeconds: 10
  podDisruptionBudget:
    enabled: true
    minAvailable: 1
cainjector:
  replicaCount: 2
  podDisruptionBudget:
    enabled: true
    minAvailable: 1

This profile enables ServiceMonitor and Gateway API, so their CRDs/controllers must already exist. Disable those options for a minimal installation without these dependencies. Replicas/PDBs do not replace node/AZ distribution and API connectivity. The current configuration uses config.gatewayAPI.enabled; the older enableGatewayAPI field remains accepted but deprecated in the 1.21.2 decoder.

Check CRD retention and actual uninstall ownership. Deleting a CRD can delete its custom resources; it is not a routine upgrade troubleshooting step.

Core Concepts ​

Certificate resources and controller-created requests

Certificate resources and controller-created requestsOpen full screen ↗
ResourceScope and purpose
CertificateDesired certificate and output Secret within a namespace
IssuerIssuance configuration referenced within its namespace
ClusterIssuerIssuance configuration referenced across namespaces
CertificateRequestCSR, issuerRef, and approval/denial status
Order/ChallengeOrdering/validation resources used by the ACME path

ClusterIssuer credential/CA Secrets live in the controller’s cluster-resource namespace, defaulting to cert-manager. Issuer Secrets live in the Issuer namespace. Permission to create a Certificate does not itself constrain SANs or issuerRef.

Certificate and renewal ​

yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: app-tls
  namespace: demo-app
spec:
  secretName: app-tls
  dnsNames: [app.example.com]
  duration: 2160h
  renewBeforePercentage: 33
  privateKey:
    algorithm: ECDSA
    size: 256
    encoding: PKCS8
    rotationPolicy: Always
  usages: [server auth]
  issuerRef:
    name: lab-ca
    kind: ClusterIssuer
    group: cert-manager.io

Requested duration can differ from what the CA issues. Default renewal occurs two-thirds through the actual X.509 lifetime, not a fixed 15/30 days before expiry. renewBeforePercentage derives the buffer from actual lifetime. Choose renewBefore or percentage, not both; duration is at least 1 hour and the effective renewal buffer must be at least 5 minutes and shorter than duration.

Version 1.21.2 renewal policy/windows and supported ARI paths can affect timing. renewal.policy:Disabled disables automatic renewal. Inspect status.renewalTime, actual notBefore/notAfter, and failure alerts. Since 1.18, privateKey.rotationPolicy defaults to Always. Updating a key/certificate Secret does not guarantee an application reload.

CertificateRequest and private keys ​

Direct requests require a real PEM CSR; do not apply truncated base64 examples. cmctl create certificaterequest creates a request in a cluster and is not an offline validation command. Restrict cmctl approve/deny permissions separately. This audit used the cert-manager PKI library to generate and verify a synthetic local key/CSR only.

Issuer Types ​

SelfSigned and CA bootstrap ​

The complete bootstrap example creates a dedicated lab root and leaf. The root explicitly uses renewal Disabled and key rotation Never to avoid unplanned trust-anchor replacement. Production root custody, offline CAs, intermediate separation, auditing, and revocation need separate design.

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager
  labels:
    trust-bundle: enabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: demo-app
  labels:
    trust-bundle: enabled
    cert-manager-http01: enabled
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: bootstrap
  namespace: cert-manager
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: lab-root
  namespace: cert-manager
spec:
  secretName: lab-root
  isCA: true
  commonName: Documentation Lab Root
  subject:
    organizations: [Documentation Lab]
  duration: 8760h
  renewal:
    policy: Disabled
  privateKey:
    algorithm: ECDSA
    size: 256
    encoding: PKCS8
    rotationPolicy: Never
  usages: [cert sign, crl sign]
  issuerRef:
    name: bootstrap
    kind: Issuer
    group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: lab-ca
spec:
  ca:
    secretName: lab-root
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: app-tls
  namespace: demo-app
spec:
  secretName: app-tls
  dnsNames: [app.example.com]
  duration: 2160h
  renewBeforePercentage: 33
  privateKey:
    algorithm: ECDSA
    size: 256
    encoding: PKCS8
    rotationPolicy: Always
  usages: [server auth]
  issuerRef:
    name: lab-ca
    kind: ClusterIssuer
    group: cert-manager.io

The CA issuer uses a Secret containing a CA certificate/private key. Replacing that Secret does not immediately reissue every leaf or update every client trust store. CA issuers can include CRL/OCSP URLs but do not generate or maintain CRLs/OCSP responses. Enforce policy preventing leaves from outliving their CA.

ACME: HTTP-01 and DNS-01 ​

ACME HTTP/DNS validation and issuance

ACME HTTP/DNS validation and issuanceOpen full screen ↗

HTTP-01 needs access to the hostname on port 80 and does not support wildcards. DNS-01 uses DNS TXT permissions for wildcard validation. Reused authorizations and ACM’s prevalidation flow mean a new challenge is not necessarily created for every request.

Do not recommend the community ingress-nginx, whose maintenance ended in March 2026, as a new-installation default. The HTTP-01 Gateway example assumes an installed Envoy Gateway and its envoy-gateway GatewayClass. Verify the actual class and align the solver namespace’s cert-manager-http01:enabled label with allowedRoutes. Gateway certificate shimming does not install a Gateway controller or implement its TLS reload.

The Route53 issuer and IAM policy specify a hostedZoneID and limit changes to challenge TXT names. Specifying the zone avoids requiring global ListHostedZonesByName access. Verify controller credentials through IRSA or supported Pod Identity and any cross-account role chain. Namespace selection and dnsZones are not substitutes for IAM authorization.

yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: public-dns01
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: pki-admin@example.com
    privateKeySecretRef:
      name: public-dns01-account
    solvers:
      - selector:
          dnsZones: [example.com]
        dns01:
          route53:
            region: ap-northeast-2
            hostedZoneID: Z1234567890ABC

Let’s Encrypt staging has rate limits and roots that production browsers do not trust. ACME accounts are environment-specific. Expiration notification emails ended in 2025; the email field does not replace expiry monitoring. Follow Retry-After and the relevant refill policy for 429 responses, rather than always retrying after one hour. ARI renewals and new issuance have different rate-limit treatment.

AWS Private CA ​

The external aws-privateca-issuer controller needs CA-ARN-scoped issuance/read permissions and an EKS workload identity. The documented actions are acm-pca:DescribeCertificateAuthority, acm-pca:GetCertificate, and acm-pca:IssueCertificate; restrict their Resource to the intended CA ARN. Replace the actual CA ARN in the example and validate CA mode, template, algorithm, usages, and requested lifetime. Check short-lived-CA limits and pricing separately. Do not bypass approval checks with disableApprovedCheck.

bash
helm repo add awspca https://cert-manager.github.io/aws-privateca-issuer
helm upgrade --install aws-pca-issuer awspca/aws-privateca-issuer \
  --version v1.9.2 --namespace cert-manager

Vault PKI ​

The Issuer/token RBAC example limits token creation to demo-app/vault-issuer. Supply the Vault server’s TLS CA Secret separately. The PKI role/policy script assumes existing PKI/Kubernetes-auth mounts, an approved Vault identity, and configured TokenReview access.

Limit SANs in the Vault role and bind audience vault://demo-app/vault-pki to this Issuer. Reviewer JWTs, Kubernetes API audiences, and OIDC access depend on where Vault runs. Do not bypass connectivity problems with tls-skip-verify or a broad default Vault policy.

The DNS role explicitly sets allow_ip_sans=false and allow_localhost=false. allowed_domains does not constrain IP SANs, and localhost has a separate permissive default. A vault write POST resets omitted role fields to defaults; read back the complete role and test allowed DNS and rejected IP/localhost requests after applying it.

EKS Integration Patterns ​

PathTLS termination and key location
ALB/NLB TLS listener with ACMAWS load balancer uses an ACM ARN
NLB TCP with Gateway/PodBackend terminates TLS using a Kubernetes Secret
Gateway HTTPS listenerGateway controller references a same-namespace TLS Secret
ACM exportable public certificateExplicitly exported key/certificate used by customer-managed workloads

ALB does not directly read a cert-manager Kubernetes Secret as a listener certificate; import/export and an ACM ARN relationship are separate steps. The ALB example requires a real ACM ARN and backend Service. The NLB TCP example passes TLS to backend port 8443, which must actually serve TLS.

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: app-gateway
  namespace: demo-app
  annotations:
    cert-manager.io/cluster-issuer: lab-ca
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      hostname: app.example.com
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - group: ""
            kind: Secret
            name: app-gateway-tls
      allowedRoutes:
        namespaces:
          from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app
  namespace: demo-app
spec:
  parentRefs:
    - name: app-gateway
  hostnames: [app.example.com]
  rules:
    - backendRefs:
        - name: app
          port: 8080

This Gateway uses lab-ca, which ordinary browsers do not trust. Real public issuance needs an approved issuer/domain-validation path and controller. Verify GatewayClass names and certificateRefs scope/type; avoid having two controllers own the same Secret.

AWS-Native Alternative: ACM + ACK ​

ACM RequestCertificate and ACK export ​

ACK ACM is itself an installed, operated open-source controller. The 1.8.1 example explicitly configures options.export:ENABLED, exportTo, and the output Secret. Defining an issuance request alone does not create a TLS Secret. In the 1.8.1 CRD (unchanged in v1.10.0), validationMethod appears only in read-only status domainValidations fields, not in spec; do not copy it into a request from old examples.

yaml
apiVersion: v1
kind: Secret
metadata:
  name: exported-public-tls
  namespace: demo-app
type: kubernetes.io/tls
data:
  tls.crt: ""
  tls.key: ""
---
apiVersion: acm.services.k8s.aws/v1alpha1
kind: Certificate
metadata:
  name: exportable-public-tls
  namespace: demo-app
spec:
  domainName: app.example.com
  keyAlgorithm: RSA_2048
  options:
    export: ENABLED
  exportTo:
    namespace: demo-app
    name: exported-public-tls
    key: tls.crt

Domain validation is separate. Route53 ACK can manage the CNAME provided by ACM, but installing the ACM controller does not validate every DNS provider automatically. Evaluate exportable-certificate pricing, controller IAM/RBAC/namespace boundaries, and private-key Secret access. Private-CA/SPIFFE mTLS identity needs its own issuer/trust design.

ACM ACME: EAB and prevalidated domains ​

Announced on July 6, 2026, ACM ACME issues 45-day public certificates. A PKI administrator prepares an endpoint/domain validation and EAB credentials associated with an IAM role, then registers clients. Changing only the server URL is insufficient. Store the HMAC key securely and provide the acm-eab Secret/hmac entry in cert-manager’s cluster-resource namespace.

yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: acm-acme
spec:
  acme:
    server: https://acm-acme-enroll.ap-northeast-2.api.aws/REPLACE_ENDPOINT_ID/directory
    email: pki-admin@example.com
    privateKeySecretRef:
      name: acm-acme-account
    externalAccountBinding:
      keyID: REPLACE_EAB_KEY_ID
      keySecretRef:
        name: acm-eab
        key: hmac

The Secret value referenced by externalAccountBinding must contain the base64url-encoded HMAC key; Kubernetes Secret data encoding is a separate layer. Preserve an already encoded provider value rather than encoding it twice. See the ACME EAB configuration.

Endpoint ID and EAB keyID are placeholders. Domains must be prevalidated at the endpoint. The ACME client generates/holds the private key and manages renewal. An ARN appearing in ACM inventory does not make this certificate directly usable with managed ALB/CloudFront/API Gateway integrations. ExportCertificate, RenewCertificate, and RevokeCertificate do not apply to this issuance path; the ACME client owns its lifecycle. Distinguish RequestCertificate for AWS-integrated services.

Service Mesh Integration ​

istio-agent, istio-csr, SDS and peer mTLS

istio-agent, istio-csr, SDS and peer mTLSOpen full screen ↗

Istio and istio-csr ​

In the sidecar flow, istio-agent creates the CSR and istio-csr submits a CertificateRequest. The agent delivers key/certificate material to Envoy through SDS; workload proxies establish mTLS. The application hop within the same Pod is not automatically mTLS.

The istio-csr values configure an actual ConfigMap volume/mount for rootCAFile. Create the application-trust bundle in cert-manager and wait for issuer readiness first. A file path alone or an empty PEM in meshConfig is insufficient. Use the current Istio Helm/istioctl external-CA settings; do not mix the istio-csr path with Kubernetes CSR RA mode. Assess Ambient support separately.

bash
helm upgrade --install cert-manager-istio-csr jetstack/cert-manager-istio-csr \
  --version v0.17.0 --namespace cert-manager --values istio-csr-values.yaml

Linkerd and trust-manager ​

The Linkerd identity example prepares the issuer Secret and root-bundle ConfigMap. Pinned control-plane chart 2026.9.1 references linkerd-identity-issuer and linkerd-identity-trust-roots.

yaml
identity:
  externalCA: true
  issuer:
    scheme: kubernetes.io/tls

This connects the control plane to an externally renewed issuer Secret, rather than copying a key once during installation. Root rollover requires overlap of old/new roots, workload renewal, and trust reload. The example lab root is manually managed with automatic renewal disabled.

trust-manager ​

The default trust-manager 0.25.0 chart renders trust.cert-manager.io/v1alpha1 Bundle. An additional CRD in the repository does not by itself change the default installed API. Source Secrets/ConfigMaps are read from the configured trust namespace.

yaml
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: application-trust
spec:
  sources:
    - secret:
        name: lab-root
        key: tls.crt
  target:
    configMap:
      key: ca-bundle.pem
    namespaceSelector:
      matchLabels:
        trust-bundle: enabled

The inline PEM field is inLine, not inlineString. Minimize public/private trust roots and explicitly select destination namespaces. Secret targets require separate enablement/RBAC; the example uses ConfigMaps. A trust bundle distributes trust anchors, not private keys.

The consumer Deployment mounts a directory. ConfigMap changes are not propagated through a subPath file mount. Even directory projection does not reload a process’s cached TLS context; test whether the application actually uses SSL_CERT_FILE or its configured trust store.

Monitoring and Troubleshooting ​

Inspect selectors and port/targetPort values in the installed chart’s ServiceMonitor. The 1.21.2 profile targets http-metrics for controller/cainjector/webhook. Do not invent a tcp-prometheus-servicemonitor port name.

yaml
groups:
  - name: cert-manager
    rules:
      - alert: CertificateNotReady
        expr: certmanager_certificate_ready_status{condition="True"} == 0
        for: 10m
        labels:
          severity: critical
      - alert: CertificateExpiringSoon
        expr: (certmanager_certificate_expiration_timestamp_seconds - time() < 604800) and (certmanager_certificate_expiration_timestamp_seconds - time() > 86400)
        for: 30m
        labels:
          severity: warning
      - alert: CertificateExpiryCritical
        expr: (certmanager_certificate_expiration_timestamp_seconds - time() <= 86400) and (certmanager_certificate_expiration_timestamp_seconds - time() > 0)
        for: 10m
        labels:
          severity: critical
      - alert: CertificateExpired
        expr: (certmanager_certificate_expiration_timestamp_seconds > 0) and (certmanager_certificate_expiration_timestamp_seconds <= time())
        for: 5m
        labels:
          severity: critical

Readiness metrics include True/False/Unknown series; an unfiltered ==0 can fire on healthy certificates. These rules passed 5 cases / 20 assertions covering healthy, NotReady, warning expiry, critical expiry, and expired certificates. Scrape/metric absence needs separate alerts.

bash
kubectl get certificates,certificaterequests -A
kubectl get orders,challenges -A
kubectl describe certificate app-tls -n demo-app
cmctl status certificate app-tls -n demo-app
# These change resources; check permissions and impact first.
cmctl renew app-tls -n demo-app

Selecting recursive DNS resolvers is not increasing propagation wait time. Inspect split-horizon DNS, CAA, TXT authorization, CNAMEs, HTTP paths, and Challenge reasons. Investigate webhook connectivity/authentication/routing before adjusting chart timeoutSeconds (1–30 seconds); there is no generic webhook-timeout flag to apply blindly.

Best Practices ​

  • The namespace Role manages Certificates without granting developers cluster-wide Secret reads. SAN/issuerRef/secretName restrictions require separate admission/approval policy.
  • Separate CertificateRequest approval from CA credential access. Check default approval-controller behavior and configure issuance policy such as approver-policy where required.
  • CA-key backups require encryption, access control, and restore tests; do not dump plaintext Secret YAML into ordinary directories. Base64 is not encryption.
  • A private CA is not an automatic fallback for a public CA: validate client trust, names, usages, key access, and recovery time.
  • secretTemplate annotations/labels are metadata; they do not activate external-secret synchronization by themselves.
  • Test renewal, CA rollover, consumer reload, and revocation separately. Schema/Helm success does not prove successful issuance.

Summary and References ​

Local checks include pinned Helm/CRD schemas, controller-config decoding, 6 renewal cases, real CSR generation/signature checking, 5 Prometheus cases / 20 assertions, 24 diagram browser cases, and sixteen synthetic issuance/CSR-signing cases in ephemeral local Vault. No external CA/ACME issuance, AWS resource creation, deployed Vault login, mesh installation, or runtime mTLS was executed.