Kubernetes cert-manager Tutorial: How to Set Up Custom CA post provides a quick overview of using cert-manager on Kubernetes, showing how to easily set up a CA with the simple CA Issuer.

Certificate management is not just a security sensitive task dealing with issuing security sensitive objects: it is a cumbersome activity including taking care of handling the reissuing of expiring certificates before they reach their end of life, delivering them to the consuming services.

In Kubernetes cert-manager Tutorial: How to Set Up Custom CA post we discuss how to simplify this process using cert-manager, automating the issuing and renewal of issued certificates.

What Is Cert-Manager

cert-manager is a Kubernetes-native tool that automates the issuance, renewal, and management of TLS certificates for workloads running in Kubernetes clusters. It supports multiple certificate authorities, integrates seamlessly with Kubernetes APIs, and securely stores certificates, simplifying the entire certificate lifecycle management.

Designed to handle the dynamic and ephemeral nature of Kubernetes workloads, cert-manager automates certificate lifecycle management at scale without manual intervention. This facilitates smooth DevSecOps integration by embedding security into the development lifecycle through automated certificate provisioning and renewal.

It supports various issuers such as:

  • ACME Issuer: Uses the ACME protocol (e.g., Let's Encrypt) to automate certificate issuance and renewal via HTTP-01 or DNS-01 challenges.
  • Simple CA Issuer: Signs certificate signing requests (CSRs) using a provided Certificate Authority (CA) bundle for internal or self-managed PKI.
  • Vault Issuer: Integrates with HashiCorp Vault to issue certificates from Vault's PKI secrets engine.
  • Venafi Issuer: Connects to Venafi Trust Protection Platform (TPP) or Venafi as a Service (VaaS) for enterprise certificate management.
  • SelfSigned Issuer: Generates self-signed certificates, useful for testing or bootstrapping a CA chain.
  • External Issuer: Enables custom certificate issuance through an external system via a validating and issuing webhook.

These issuers enable you to request certificates to both private as well as globally available CAs. This means that:

  • when dealing with certificates for public domains, you can use the ACME Issuer with Let's Encrypt
  • when dealing with certificates for local domains (which canot be issued by glabal CAs), you can use the ACME Issuer with an on premises Step CA instance running your own private CA.

If your corporate is running Hashicorp's Vault or Venafi TPP or VaaS, you can use their specific issuer.

You can even handle corner cases such as any other CA manageable by API or scripting, such as Cloudflare's CFSSL PKI and TLS Toolkit: in such a scenario, you can integrate cert-manager to those CAs using the External issuer, despite this is a cumbersome integration, since it requires you to write the code to implement the necessary webhooks.

Last but no least, if your needs are quite basic, such as when dealing with labs or testing environments, you can use the SelfSigned issuer to generate a self-signed ROOT CA certificate in conjunction with the Simple CA Issuer to sign certificate requests.

The SimpleCA Issuer does not support extensions such as CRL and OCSP: if you are dealing with production security sensitive environments requiring to be able to quickly revoke the issued certificates, consider using the ACME issuer or Vault or Venafi, or the External issuer with a CA supporting CRL and or OCSP.

In Kubernetes-based scenarios, cert-manager is preferred over traditional tools due to its deep Kubernetes integration, flexibility in issuer options, and native automation capabilities. These features make it ideal for dynamic, containerized environments where manual certificate management is complex and error-prone.

Moreover, mind that, despite very often cert-manager is used to generate certificates consumed by applications running inside Kubernetes, it is possible to use it also to sign certificates for applications running outside it.

This post is meant only at showing how to set up cert-manager and at providing an overview of how to setup an automated CA: to maximize compatibility, it illustrates how to use the Simple CA issuer, since it does not introduce any external dependency on third part products. To keep the post short and focused, we also don't delve in security hardening (which I may put in a dedicated post later on).

Deploy The Cert-Manager Suite

Let's start the workshop by showing how to set up cert-manager: it can be easily deployed using its official Helm chart, which is available in Jetstack's Helm repository.

if you still don't have the helm command line utility installed, you can install it as follows:

sudo mkdir -m 755 -p /opt/helm /opt/helm/bin
curl -sL https://get.helm.sh/helm-v3.17.3-linux-amd64.tar.gz \
| sudo tar xvz --strip-components=1 -C /opt/helm/bin

the above statements create the /opt/helm/bin directory and download and installs helm v3.17.3 for x86_64 systems beneath it.

Of course, adjust the second command  to match the Helm version and architecture you want to install (if you are using an AARM, replace amd64 with arm64).

Let's add to the system-wide PATH as follows:

echo PATH=\${PATH}:/opt/helm/bin | sudo tee -a /etc/profile.d/helm

We must then disconnect and reconnect, or source the /etc/profile.d/helm file to import it in the current session:

source /etc/profile.d/helm

We are now able to run the helm command line tool, so as our first command, let's add the Jetstack Helm repository by running:

helm repo add jetstack https://charts.jetstack.io

Let's verify the outcome:

helm repo list

If everything is properly working, the jetstack repository must be in the returned list.

Install cert-manager Via Helm

The first component to provision is cert-manager itself. Just run:

helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.16.2 \
  --set crds.enabled=true

In this example we deploy version v1.16.2 it in the cert-manager namespace.

We also specify the crds.enable option so to deploy  the Custom Resource Definitions (CRD) as part of the Helm installation.

If everything works as expected, you must get a message like the below one:

NAME: cert-manager
LAST DEPLOYED: Sat Oct  4 14:07:29 2025
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.16.2 has been deployed successfully!

Install trust-manager Via Helm

The cert-manager suite provides also trust-manager, which is a component providing a Kubernetes operator which automates the aggregation, generation, and lifecycle management of trust-stores.

These bundles can be output in various formats such as PEM, JKS, or PKCS#12, and are distributed as ConfigMaps for consumption by workloads and cluster components.

Trust-manager consolidates CA certificates from multiple sources, including private CAs issued and managed by cert-manager as Kubernetes Secrets, as well as globally recognized root CAs, into standardized trust bundles. This last one is particularly important for containers that do not include a comprehensive system-wide trust store by default.

The trust-manager operator continuously reconciles and updates these trust bundles to ensure that all consuming applications have access to current and consistent trust anchors, thereby facilitating secure TLS communication and certificate validation within dynamic Kubernetes environments.

In this example we install it in the cert-manager namespace, waiting until the process completes:

helm upgrade trust-manager jetstack/trust-manager \
  --install \
  --namespace cert-manager \
  --wait

If everything works as expected, you must get a message like the below one:

NAME: trust-manager
LAST DEPLOYED: Sat Oct  4 14:09:44 2025
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
⚠️  WARNING: Consider increasing the Helm value `replicaCount` to 2 if you require high availability.
⚠️  WARNING: Consider setting the Helm value `podDisruptionBudget.enabled` to true if you require high availability.

trust-manager v0.19.0 has been deployed successfully!
Your installation includes a default CA package, using the following
default CA package image:

quay.io/jetstack/trust-pkg-debian-bookworm:20230311-deb12u1.0

It's imperative that you keep the default CA package image up to date.

Install cert-manager-CSI-Driver Via Helm

Another component provided by the cert-manager suite is the cert-manager’s CSI driver: this is a Container Storage Interface (CSI) driver which allows Kubernetes pods to request certificates and to consume them as files mounted directly into their container’s filesystem.

The CSI driver works alongside cert-manager to automate the certificate lifecycle by handling key and Certificate Signing Request (CSR) creation, certificate issuance, and renewal.

When certificates are renewed, the CSI driver updates the mounted certificate files inside the container’s filesystem automatically

Be wary that, whether the application uses the updated certificates without restart depends on the application’s ability to reload certificates dynamically. If the application only loads certificates at startup, the pod or container must be restarted to pick up the renewed certificates.

In this example we install the CSI driver in the cert-manager namespace, along with cert-manager.

Just run:

helm upgrade cert-manager-csi-driver jetstack/cert-manager-csi-driver \
  --install \
  --namespace cert-manager \
  --wait

If everything works as expected, you must get a message like the below one:

NAME: cert-manager
NAME: cert-manager-csi-driver
LAST DEPLOYED: Sat Oct  4 14:13:57 2025
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None

if you are curious, you can get more information on this CSI driver by running:

kubectl describe csidrivers csi.cert-manager.io

Mastering a custom CA is just one small pillar of what is required from a DevOps and DevSecOps Linux professional. If you want to systematically fill your DevOps and DevSecOps knowledge gaps by reading a crash-course like book full of hands-on enterprise exercises, jump directly to the Apress Blueprint Box below to discover how to boost and evolve your career using a self-paced learning path.

Working With The Simple CA Issuer

In this lab we have the first go with cert-manager by creating a private single-tier Certificate Authority (CA) using the simple CA issuer.

As said, this is the built-in CA issuer type, suitable for quite basic internal or self-signed certificate needs. Although it's not a full-fledged PKI system, since it does not have any third part requirements, it's ideal for basic private CA setups such as labs, testing and user acceptance environment.

Create The Self-signed Issuer

The first step for setting up a private CA is defining a selfSigned Issuer in the same namespace where we deployed cert-manager.

This component is used to generate self-signed certificates, which are the kind of certificate used by ROOT CAs.

Just create the self-issuer.yml manifest file with the following contents:

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: issuer.selfsigned
  namespace: cert-manager
spec:
  selfSigned: {}

then, submit it to Kubernetes by running:

kubectl create -f self-issuer.yml

Generate The CA's Self-Signed Certificate

Once done, we are ready to generate the CA's ROOT certificate.

Just create the carcano-t1-root01.yml file with the following contents:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: carcano-t1-root01
  namespace: cert-manager
spec:
  isCA: true
  commonName: carcano-t1 ROOT01 CA
  subject:
    organizations:
      - Carcano SA
    organizationalUnits:
      - Services
  secretName: carcano-t1-root01-ca
  privateKey:
    algorithm: ECDSA
    size: 521
  # 10 years
  duration: 87600h
  # 7 years
  renewBefore: 61320h
  issuerRef:
    name: issuer.selfsigned
    kind: Issuer
    group: cert-manager.io

In this example we are creating the carcano-t1 ROOT01 CA certificate with a 10 years lifetime, automatically renewed after 7 years.

It is a common practice to renew the CA's certificate early enough to avoid issued certificates overstepping the CA's certificate: renewing 3 years before the expiration is a reasonable boundary.

We can now submit this manifest to Kubernetes by running:

kubectl create -f carcano-t1-root01.yml

The cert-manager operator immediately takes care of enrolling the certificate.

We can check that the certificate was actually issued by running:

kubectl -n cert-manager get certificate

The output must look like as follows:

NAME                READY   SECRET                 AGE
carcano-t1-root01   True    carcano-t1-root01-ca   7s
trust-manager       True    trust-manager-tls      5m36s

beside the certificate generation, cert-manager took also care of creating the carcano-t1-root01-ca secret in the cert-manager namespace. 

We can view it by running:

kubectl -n cert-manager get secret carcano-t1-root01-ca

the output must look like as follows:

NAME                   TYPE                DATA   AGE
carcano-t1-root01-ca   kubernetes.io/tls   3      53s

Create A ClusterCA ClusterIssuer

We must now create the carcano-t1 ROOT01 leaf certificate's issuer component. Since we want it to be able to enroll certificates also from namespaces other than the one where cert-manager is running, we define it of kind ClusterIssuer.

Create the carcano-t1-root01-issuer.yml manifest file with the following contents:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: issuer.carcano-t1-root01
spec:
  ca:
    secretName: carcano-t1-root01-ca

submit the manifest to Kubernetes by running:

kubectl create -f carcano-t1-root01-issuer.yml

This creates the issuer.carcano-t1-root01 ClusterIssuer.

Once invoked, this cluster issuer will always enroll certificates using the carcano-t1 ROOT01 CA's certificate.

Configure Trust-Stores

As we said, cert-manager enables also the automatic lifecycle management of trust-stores. In a typical scenario, you would need three kind of trust stores:

  • global-ca: a global CA bundle, containing only the ROOT certificates of the global available CAs. This kind of trust-store is useful when dealing with container images which not embed this kind of trust store, or when you don't want to be forced to generate a new container image version of your running containers whenever you need to update the globally available ROOT CA's certificates.
  • cluster-ca-only: a cluster CA bundle, containing only the CA's certificate of a specific private CA - you will need a distinct one for each single private CA
  • cluster-ca-extended: a cluster CA bundle, containing the CA's certificate of a specific private CA along with all the ROOT certificates of the global available CAs - you will need a distinct one for each single private CA

Create The Global CA Bundle

Let's start our experiment by creating the globalca-bundle.yml manifest file with the following contents:

apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: globalca-bundle 
spec:
  sources:
    - useDefaultCAs: true
  target:
    configMap:
      key: "globalca-bundle.pem"

as you see, in the sources dictionary we specify only the useDefaultCA directive, which tells to add to the list the bundle with all the global CAs.

Once create the above manifest file, submit it to Kubernetes by running:

kubectl create -f globalca-bundle.yml

the outcome is the creation of the globalca-bundle Bundle, which triggers the creation of the globalca-bundle ConfigMap in every namespace.

You can verify this by checking a few ones, such as:

kubectl -n cert-manager get configmap globalca-bundle
kubectl -n default get configmap globalca-bundle 

for every namespace you must get:

NAME              DATA   AGE
globalca-bundle   1      4m55s

If you fancy, you can easily inspect its contents by running:

kubectl -n default get configmap globalca-bundle -o jsonpath='{.data.globalca-bundle\.pem}' 

in this example we are inspecting the on in the default namespace.

Mind that it is not necessary to inspect them all, since, as mentioned, the contents of the globalca-bundle ConfigMap are consistent across all namespaces.

Create The ClusterCA-Only Bundle

Let's now create a clusterca-only bundle dedicated to the carcano-t1 ROOT01 CA we just set up.

Create the carcano-t1-root01-ca-only.yml manifest file with the following contents:

apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: carcano-t1-root01-ca-only
spec:
  sources:
    - useDefaultCAs: false
    - secret:
        name: "carcano-t1-root01-ca"
        key: "tls.crt"
  target:
    configMap:
      key: "clusterca.pem"

as you see, in the sources dictionary we this time the useDefaultCA directive is set to false, and we provide the coordinates to the secret containing the the carcano-t1 ROOT01 CA certificate.

Once done with the above file,  submit it to Kubernetes by running:

kubectl create -f carcano-t1-root01-ca-only.yml

We can then inspect its contents by running:

kubectl -n default get configmap carcano-t1-root01-ca-only -o jsonpath='{.data.clusterca\.pem}' 

Create The ClusterCA-extended Bundle

Let's finally create a clusterca-extended bundle containing  the carcano-t1 ROOT01 CA we just set up along with all the globally available ROOT CAs' certificates.

Create the  carcano-t1-root01-ca-extended.yml manifest with the following contents:

apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: carcano-t1-root01-ca-extended
spec:
  sources:
    - useDefaultCAs: true
    - secret:
        name: "carcano-t1-root01-ca"
        key: "tls.crt"
  target:
    configMap:
      key: "clusterca.pem"

as you see, this time the sources list contains both the useDefaultCAs option to true along with the coordinates to the secret containing the the carcano-t1 ROOT01 CA certificate.

Let's submit the manifest to Kubernetes by running:

kubectl create -f carcano-t1-root01-ca-extended.yml

Let's ensure that carcano-t1-ca-extended bundle contains all the global's CAs certificates along with the carcano-t1 ROOT01 certificate - first, let's dump the carcano-t1-root01-ca-extended bundle into the ca-chain.pem file by running:

kubectl -n default get configmap carcano-t1-root01-ca-extended -o jsonpath='{.data.clusterca\.pem}' > ca-chain.pem

Then, we can lookup for the carcano-t1 ROOT01 CA as follows:

openssl crl2pkcs7 -nocrl -certfile ca-chain.pem | openssl pkcs7 -print_certs

Mastering a Custom CA is Just One Brick in the DevSecOps Wall

Setting up cert-manager and managing trust bundles with trust-manager is a fantastic skill, but certificate management is just one small pillar of what is required from a modern DevOps and DevSecOps professional on Linux. Real-world infrastructures demand a massive, interconnected skill set.

If you look at your daily workflows, are you confident across the entire stack? Or do you feel you have gaps holding your career back?

My book, "DevSecOps and DevOps for Linux: The Foundations", published by Apress, was specifically designed to solve this. It is a comprehensive, lab-driven blueprint tailored for both students and professionals to systematically fill their knowledge gaps through intensive, hands-on enterprise exercises — built entirely on open-source, cloud-agnostic architectures to ensure zero vendor lock-in.

Key insights covered in this volume:

  • The Holistic Skills Set Brick: Bridge technical engineering with team management frameworks. Master Scrum, Kanban, and Lean methodologies to design system architectures aligned with real corporate workflows.
  • The Shell Scripting & Unix Tools Brick: Build rigorous operational foundations. Master advanced Bash shell scripting architecture while learning how to combine core Unix tools into robust, repeatable, and enterprise-ready host automations.
  • The Version Control Engineering Brick: Move past basic commits. Dive deep into Git version control, mastering feature-branch workflows, repository lifecycle management, and complex conflict resolution.
  • The Data & Core Automation Brick: Build bulletproof data processing setups. Learn advanced RegEx, how to operate using evergreen tools such as Grep, Sed, and AWK, and how to master structured data parsing (XML, JSON, YAML) using Python and tools like xmlstarlet, jq, and yq.
  • The Modern Python & Automation Brick: Develop a modern Python project using pyproject.toml with pytest-based unit tests, governing the project with GNU Make for testing, building, and digitally signing RPM packages. The project is presented in an evolving fashion, showing how features are added step by step, highlighting how a properly structured Python project can be improved and evolved with minimal or no rework at all.
  • The Linux OS Hardening & PKI Brick: Learn the real mechanics of security. Implement X.509/PKI architectures, TLS configurations, and GPG encryption and signing, while mastering low-level kernel defenses like SELinux and Linux Capabilities.
  • The Compliance Check and Shift-Left Security Brick: Learn how to leverage the pre-commit framework to automate compliance checks with Pylint and Flake8, and perform security scans with Bandit and Safety, extending the security audit to the full software supply chain.
  • The Application Integration Brick: Master the foundational protocols used to securely interconnect enterprise microservices, including HTTP, REST, OpenAPI, SOAP, and LDAP/LDAPS.
  • The Infrastructure Delivery Brick: Put theory into practice with vertical, real-world labs. Move from basic scripts to engineering Ansible architectures, rootless Podman setups, image creation via Buildah, and complete Pulp3 deployments using Docker Compose.
  • The Enterprise GitOps Pipeline Brick: Tie everything together by automating your software supply chain. Build complete continuous deployment workflows using Gitea CI pipelines hosted natively on Kubernetes (RKE2).

Footnotes

In this tutorial, we successfully engineered a powerful internal PKI simulator by deploying cert-manager and trust-manager to bootstrap a custom PKI. We moved from a self-signed root authority to a cluster-wide CA Issuer, building multi-format trust stores.

While the lack of CRL and OCSP keeps it lean, this lightweight architecture is an invaluable asset for enterprise sandboxes, development environments, and QA testing, giving developers and DevOps engineers a fully functional playground to validate complex TLS setups safely and rapidly.

With this high-velocity testing foundation fully operational, you are now ready to see how workloads actually consume this trust material.

In the next post, we will bridge the gap to deployment by exploring how to generate Server and Client TLS certificates for rigid mTLS communication. We will also dive into advanced security by using the cert-manager CSI Driver to mount certificates directly into Pod memory, before wrapping up with production-ready Helm charts to automate the entire lifecycle.

As usual, if you appreciate this post and any other ones, just share this and the others on Linkedin - sharing and comments are an inexpensive way to push me into going on writing - this blog makes sense only if it gets visited.

Also concrete contributions to the maintenance of this blog are also very welcome, ... just put your tip in the below cup.

I hate blogs with pop-ups, ads and all the (even worse) other stuff that distracts from the topics you're reading and violates your privacy. I want to offer my readers the best experience possible for free, ... but please be wary that for me it's not really free: on top of the raw costs of running the blog, I usually spend on average 50-60 hours writing each post. I offer all this for free because I think it's nice to help people, but if you think something in this blog has helped you professionally and you want to give concrete support, your contribution is very much appreciated: you can just use the above button.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>