cert-manager mints the TLS and mTLS identities of a Kubernetes cluster, so whoever can create a Certificate against a powerful issuer can obtain a trusted identity for any name that issuer will sign. In most clusters cert-manager runs with defaults: every request to a built-in Issuer or ClusterIssuer is auto-approved, a ClusterIssuer serves every namespace, and CA keys and DNS provider credentials sit in Secrets in the cert-manager namespace. That is fine for public ACME certificates on a single-team cluster and dangerous for an internal CA that backs service-to-service authentication. This guide covers the cert-manager trust model, issuer design, approval policy, private key settings, installation hardening, monitoring and a phased rollout. It is for platform and PKI engineers. Read the ACME and Automated Certificate Management Guide and the Machine Identity, PKI and Certificate Lifecycle Guide for the wider context.
Key takeaways
- Auto-approval is the default. cert-manager approves every CertificateRequest for
cert-manager.ioIssuers and ClusterIssuers unless you setdisableAutoApprovaland deploy a policy approver such as approver-policy. - A ClusterIssuer is a cluster-wide signing service. Any namespace whose users can create Certificates can request names from it; reserve ClusterIssuers for public ACME and use namespaced Issuers for internal CAs.
- The cluster resource namespace holds the crown jewels. ClusterIssuer CA keys, ACME account keys and DNS API tokens live as Secrets in
cert-managerby default; treat read access there as access to your PKI. - Rotate keys on every issuance. Since v1.18 the default
privateKey.rotationPolicyisAlways; older installs and explicitNeversettings keep one private key for years. - Monitor expiry from metrics, not tickets.
certmanager_certificate_expiration_timestamp_secondsandcertmanager_certificate_ready_statuscatch failed renewals before an outage.
How cert-manager issues certificates and where trust sits
A Certificate resource describes the desired certificate. The controller generates a private key into the target Secret, creates a CertificateRequest carrying the CSR, waits for it to be Approved, and the issuer signs it. The Approved and Denied conditions are permanent and mutually exclusive, and the webhook uses the SubjectAccessReview API to check that whoever sets them holds the approve verb. cert-manager records the requesting username and groups on each request and rejects attempts to change them; for requests created from a Certificate the requester is cert-manager itself.
| Component or asset | Why it matters | Who should reach it |
|---|---|---|
| Controller | Creates and modifies Secrets cluster-wide and caches TLS Secrets in memory | No human exec; dedicated nodes |
| cainjector | Injects CA bundles into webhooks and CRDs; also caches certificate data | No human exec |
| Webhook | Validates resources and approval changes | API server only |
| CA key Secret for a CA issuer | Signs any certificate the issuer allows | Controller only; break-glass for humans |
| ACME account key and DNS01 credentials | Prove control of your domains to a public CA | Controller only |
approve on signers | Lets a principal approve or deny requests | The approver deployment only |
Issuer design
An Issuer is namespaced and cannot issue Certificates in other namespaces; a ClusterIssuer can issue across all namespaces and reads its referenced Secrets from the cluster resource namespace (cert-manager unless the controller runs with --cluster-resource-namespace). In-tree issuer types are SelfSigned, CA, Vault, CyberArk and ACME (HTTP01 and DNS01).
| Issuer type | Typical use | Main risk | Recommended scope |
|---|---|---|---|
| ACME (public CA) | Internet-facing ingress | DNS01 token that can edit whole zones; HTTP01 challenge hijack via ingress | ClusterIssuer is acceptable, with a policy limiting names per namespace |
| CA | Internal mTLS, webhooks | CA private key stored in a Kubernetes Secret | Namespaced Issuer per trust domain; prefer an intermediate, never a root |
| Vault or CyberArk | Enterprise PKI with external policy | The issuer's auth credential to the PKI | Namespaced, with role limits enforced in the PKI too |
| SelfSigned | Bootstrapping a CA | Creates untracked trust anchors | Restrict to the platform namespace |
- Keep the CA key out of the cluster where possible: an external issuer backed by Vault, a cloud private CA or an HSM means a cluster compromise yields signing access only while the attacker holds the issuer credential, not a copy of the key. See the AWS Certificate Manager and Private CA Guide.
- Scope DNS01 credentials: give the DNS solver a token or IAM role limited to the specific zones or records it must change, never an account-wide DNS administrator.
- Name constraints are not checked by built-in issuers: the CA and SelfSigned issuers do not check SANs against a CA's name constraints, so clients may reject certificates in a TLS handshake. Use policy to limit names, not the CA certificate alone.
Approval policy: replacing auto-approval
approver-policy evaluates CertificateRequestPolicy resources (policy.cert-manager.io/v1alpha1, cluster-scoped). A policy applies to a request only if it is both selected by its selector and bound by RBAC to the requester with the use verb on certificaterequestpolicies. If any matching policy permits the request it is approved; if matching policies exist but none permit it, it is denied; with no matching policy the request is left unprocessed.
| Policy field | Behaviour | Example use |
|---|---|---|
allowed | Lists permitted attributes; omitted fields are deny all | dnsNames limited to *.team-a.svc.cluster.local; isCA not allowed |
constraints | Limits; omitted constraints are allow all | maxDuration: 720h, privateKey.algorithm: ECDSA, minSize: 256 |
selector.issuerRef | Which issuers the policy covers | The internal CA Issuer only |
selector.namespace | matchNames or matchLabels | Namespaces labelled for team-a |
- Turn off the built-in approver: set the Helm value
disableAutoApproval=true(v1.15.0 and later) and, if some external issuers should stay auto-approved, list them inapproveSignerNames. An empty list approves everything. - Bind the right subject: for Certificate resources the requester is the cert-manager controller service account, so bind policies to it and use the namespace selector to distinguish tenants.
- Restrict the approve verb: grant
approveonsignersincert-manager.ioonly to the approver, with resource names such asissuers.cert-manager.io/<namespace>.<name>. The verb covers both approving and denying.
Private keys, lifetimes and Secrets
| Setting | Default | Recommendation |
|---|---|---|
privateKey.rotationPolicy | Always from v1.18 (previously Never) | Keep Always; audit for explicit Never |
duration | 90 days (minimum 1 hour) | Shorter for internal mTLS, aligned with policy maxDuration |
renewBefore / renewBeforePercentage | Renew at two thirds of lifetime | Prefer renewBeforePercentage to avoid renewal loops |
usages | Digital signature, key encipherment, server auth | Add client auth only where mTLS clients need it |
--enable-certificate-owner-ref | Off: the Secret survives deletion of the Certificate | Enable, or clean up orphaned key Secrets |
revisionHistoryLimit | 1 from v1.18 | Keep low so old requests do not accumulate |
keystores.pkcs12 | Not created unless set | Use passwordSecretRef and the Modern2023 profile when Java clients need PKCS#12 |
- Reissue, do not delete: use
cmctl renewto rotate keys; the docs advise against deleting the Secret. - Do not use
ca.crtas a trust store: it changes on renewal. Distribute trust bundles separately. - Protect the Secret: the key Secret is base64 in etcd like any other. See the Kubernetes Secrets Security Guide for encryption at rest and RBAC on Secrets.
Where identity fits
Every certificate cert-manager issues for mTLS is a workload identity, and every issuer credential (CA key, ACME account key, DNS token, Vault role) is a non-human identity with the power to create more identities. Inventory them with owners and lifetimes like any other machine credential. ATT&CK tracks the attacker side as T1649 Steal or Forge Authentication Certificates; see the MITRE ATT&CK T1649 Steal or Forge Authentication Certificates Guide and the Kubernetes NHI Security Guide.
Hardening the installation
- NetworkPolicy: enable
networkPolicy.enabled: truebut tighten it. The project calls the chart defaults over-permissive (inbound to all listening ports; outbound TCP 443, 6443, TCP/UDP 53 and TCP 80). Allow metrics on TCP 9402 only from your collector. - Dedicated nodes: because the controller and cainjector cache TLS Secrets in memory, run them on tainted node pools with
nodeSelectorandtolerations. - Reduce cainjector reach: where it serves only cert-manager's own webhook, run it with
--namespace=cert-managerand--enable-certificates-data-source=false. - Service account tokens: set
automountServiceAccountToken: falseon pods and service accounts and mount a read-only projected token explicitly, as in the project'svalues.best-practice.yaml. - Availability: two controller and cainjector replicas, three or more webhook replicas, a PodDisruptionBudget with
minAvailable: 1andpriorityClassName: system-cluster-critical, because a dead webhook blocks certificate changes. - Stay supported: the project supports at least two releases, each for about four months, and only the latest patch of each.
Detection and measurement
cert-manager exposes Prometheus metrics on port 9402 with the certmanager_ prefix, and the cert-manager-mixin project bundles alerts and dashboards.
| Signal | Source | Alert when |
|---|---|---|
certmanager_certificate_expiration_timestamp_seconds | Metrics | Less than one third of lifetime left and no recent renewal |
certmanager_certificate_ready_status | Metrics | Condition not True for longer than an hour |
certmanager_certificate_renewal_timestamp_seconds | Metrics | Renewal time passed without a new certificate |
certmanager_controller_sync_error_count, certmanager_http_acme_client_request_count | Metrics | Sustained errors or ACME rate-limit responses |
CertificateRequests with Denied | Kubernetes API | Any denial for a namespace that should not request that name |
| Reads of issuer Secrets, new Issuers or ClusterIssuers | Kubernetes audit log | Any principal other than the controller or platform admins |
Metrics that show the programme is working:
- Certificates expiring within 7 days that are not Ready (target 0).
- Percentage of issuers covered by an approver-policy rule (target 100% of internal issuers).
- Certificates with
rotationPolicy: Never(trending to 0). - CA keys held in Kubernetes Secrets rather than an external PKI or HSM.
- Median certificate lifetime for internal mTLS.
Phased rollout
- Inventory: list Issuers, ClusterIssuers, their types, where their keys and credentials live, and which namespaces use them.
- Harden the install: apply the best-practice Helm values, NetworkPolicy and dedicated nodes in staging; confirm issuance still works, including ACME HTTP01 through your ingress (v1.18 changed the challenge Ingress
PathTypetoExact). - Write approver policies: deploy approver-policy alongside the default approver first and write
CertificateRequestPolicyrules for each issuer. - Switch approval: set
disableAutoApproval=truein staging, watch for requests left unprocessed (no matching policy), fix rules, then repeat in production. - Move internal CAs out: replace CA issuers holding root or intermediate keys in Secrets with an external issuer where feasible.
- Rollback: re-enable auto-approval with the Helm value; issued certificates and their Secrets are unaffected, and pending requests proceed.
Framework mapping
| Practice | NIST CSF 2.0 | NIST SP 800-53 r5 | MITRE ATT&CK |
|---|---|---|---|
| Certificate issuance and workload authentication | PR.AA-03 | SC-17, IA-5 | T1649 Steal or Forge Authentication Certificates |
| Issuer key and credential protection | PR.AA-01 | SC-12 | T1552.004 Private Keys |
| Approval policy and least privilege | PR.AA-05 | AC-6 | T1587.003 Digital Certificates |
| Installation hardening | PR.PS-01 | CM-6, CM-7 | None specific |
| Expiry and issuance monitoring | DE.CM-09 | AU-6 | None specific |
Common failure patterns
- Internal CA as a ClusterIssuer with auto-approval: any team can mint a certificate for another team's service name and impersonate it over mTLS.
- Root CA key in a Secret: a SelfSigned bootstrap left the root in
cert-manager, readable by anyone with Secret read in that namespace or etcd access. - Account-wide DNS credentials: the DNS01 token can rewrite any record in the provider account, which is far more than certificate issuance needs.
- Static private keys:
rotationPolicy: Nevercarried over from older versions, so a stolen key stays valid across every renewal. - Silent renewal failure: rate limits, a broken ingress challenge or a removed DNS permission go unnoticed until the certificate expires. See the Certificate Expiry Outages and 47-Day TLS Certificates Guide.
Practitioner checklist
- Set
disableAutoApproval=trueand enforce issuer rules with approver-policy. - Use namespaced Issuers for internal CAs; keep ClusterIssuers for public ACME.
- Restrict Secret read in the cluster resource namespace to the controller and break-glass admins.
- Scope DNS01 credentials to the zones they serve.
- Keep
privateKey.rotationPolicy: Alwaysand cap durations with policy. - Apply the best-practice Helm values: tight NetworkPolicy, dedicated nodes, explicit projected tokens.
- Alert on expiry, not-Ready certificates and renewal errors from cert-manager metrics.
- Stay on a supported release and review release notes for default changes.
Standards and references
- cert-manager: Best Practice installation
- cert-manager: CertificateRequest and approval
- cert-manager: approver-policy
- cert-manager: Certificate resource
- cert-manager: Issuer concepts
- cert-manager: Configuration and cluster resource namespace
- cert-manager: Release notes 1.18
- cert-manager: GitHub repository
Related NHI Mgmt Group resources: ACME and Automated Certificate Management Guide · Machine Identity, PKI and Certificate Lifecycle Guide · Kubernetes NHI Security Guide · Certificate Revocation Guide