Setting up cert-manager and self-signed CA
Use cert-manager to handle certificates in your cluster
Although Traefik has some capabilities to handle certificates, it is better to use a service specialized on such task. Enter cert-manager, a popular certificate management service in the Kubernetes landscape.
Deploying cert-manager
At the time of writing this, there is no official Kustomize way for deploying cert-manager. The closest method is by applying a YAML manifest, but you can build your own Kustomize procedure with it (as you have done for the metrics-server deployment).
In your
kubectlclient system, create a folder structure for the cert-manager deployment project:$ mkdir -p $HOME/k8sprjs/cert-manager/deployment/The deployment project has to be put in its own
deploymentsubfolder because, later, you will need to create another project for creating a self-signed root CA (Certificate Authority).Create a
kustomization.yamlfile in thedeploymentsubfolder:$ touch $HOME/k8sprjs/cert-manager/deployment/kustomization.yamlDeclare the cert-manager setup in the
kustomization.yamlfile:# cert-manager setup apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - https://github.com/cert-manager/cert-manager/releases/download/v1.19.0/cert-manager.yamlNote
Find the URL for the newest cert-manager version in its official installation procedure with
kubectl
You can also find the YAML file in the assets list of each release.Deploy cert-manager with
kubectl:$ kubectl apply -k $HOME/k8sprjs/cert-manager/deployment/This command prints a long output of lines indicating the many components created in the deployment of cert-manager.
Verifying the deployment of cert-manager
After the deployment has finished successfully, give it around a minute to allow cert-manager to initialize itself and start its pods. Then, you can verify that cert-manager has deployed properly just by checking if its pods are Running:
$ kubectl -n cert-manager get pods
NAME READY STATUS RESTARTS AGE
cert-manager-b5cc5b7c5-84x2m 1/1 Running 0 63s
cert-manager-cainjector-7cf6557c49-2bdr2 1/1 Running 0 63s
cert-manager-webhook-58f4cff74d-v6chb 1/1 Running 0 63sNotice the namespace cert-manager specified with the -n option in the kubectl command. The cert-manager service deploys itself in its own cert-manager namespace.
Note
Use kubectl to discover the namespaces existing in your cluster
Get a list of all the existing namespaces within your K3s cluster with kubectl like this:
$ kubectl get namespaces
NAME STATUS AGE
cert-manager Active 94s
default Active 14d
kube-node-lease Active 14d
kube-public Active 14d
kube-system Active 14d
metallb-system Active 11dInstalling the cert-manager plugin in your kubectl client system
To help you to manage the certificates you put in your cluster, cert-manager offers an independent command line tool. You have to install it in your kubectl client system, then add it to your user’s $PATH.
You can install this cert-manager command line tool in a kubectl client system like the one configured previously as follows:
From the cert-manager command line tool GitHub releases page, download the
tar.gzfile of the latest release available (v2.3.0 when writing this):$ wget https://github.com/cert-manager/cmctl/releases/download/v2.3.0/cmctl_linux_amd64.tar.gz -O $HOME/bin/cmctl_linux_amd64.tar.gzExtract the content of the downloaded
cmctl_linux_amd64.tar.gz:$ cd $HOME/bin $ tar xf cmctl_linux_amd64.tar.gzThe
tarcommand extracts these files:The command line tool as a
cmctlbinary.A
LICENSEand aREADME.mdfile that you can remove together with thecmctl_linux_amd64.tar.gz:$ rm cmctl_linux_amd64.tar.gz LICENSE README.md
Restrict the binary’s permissions:
$ chmod 700 cmctlMake a symbolic link to the new
cmctlcommand namedkubectl-cert_manager:$ ln -s cmctl kubectl-cert_managerThis allows you to use the
cmctlcommand as a plugin integrated withkubectl.Test
cmctlwithkubectlby checking its version:$ kubectl cert-manager version Client Version: util.Version{GitVersion:"v2.3.0", GitCommit:"29b59b934c5a6f533b2d278f4541dca89d1eb288", GitTreeState:"", GoVersion:"go1.24.5", Compiler:"gc", Platform:"linux/amd64"} Server Version: &versionchecker.Version{Detected:"v1.19.0", Sources:map[string]string{"crdLabelVersion":"v1.19.0"}}You can also check if the cert-manager API is accessible:
$ kubectl cert-manager check api The cert-manager API is ready
Note
The cert-manager’s kubectl plugin has other commands available
Check them all out in its official page.
Setting up self-signed CAs for your cluster
You have the tools deployed for handling certificates in your cluster. Now you can create a structure of self-signed Certification Authorities (_CA_s) for issuing certificates.
Preparing the Kustomize folder structure
Create a folder structure for a Kustomize subproject within the already existing cert-manager path:
$ mkdir -p $HOME/k8sprjs/cert-manager/certificates/resourcesRoot CA
The root CA is the head of your CA structure. This CA only certifies the intermediate CAs that do take care of issuing certificates for apps or services:
In the
resourcesdirectory, create these empty YAML files:$ touch $HOME/k8sprjs/cert-manager/certificates/resources/{homelab.cloud-root-ca-issuer-selfsigned.cluster-issuer,homelab.cloud-root-ca-tls.certificate,homelab.cloud-root-ca-issuer.cluster-issuer}.cert-manager.yamlIn the
homelab.cloud-root-ca-issuer-selfsigned.cluster-issuer.cert-manager.yamlfile, configure the self-signed root CAClusterIssuerfor your entire cluster:# Self-signed cluster-wide issuer for the root CA's certificate apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: homelab.cloud-root-ca-issuer-selfsigned spec: selfSigned: {}This cluster issuer will be dedicated only to “self-sign” your root CA’s certificate:
The
apiVersionpoints to the cert-manager API, not to the Kubernetes one.The
kindisClusterIssuer(a cert-manager kind, not a Kubernetes one), meaning this particular issuer will be available for all the namespaces in your cluster.The
nameis a descriptive string, like the YAML filename.Within the
specsection, you see the empty parameterselfSigned. This means that this issuer is of the simplest type you can have, the self-signed one. It is not trusted by browsers, but it is enough to generate certificates that you can use within your own local or home network.
Issue a certificate with the self-signed root CA issuer in
homelab.cloud-root-ca-tls.certificate.cert-manager.yaml:# Certificate for root CA apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: homelab.cloud-root-ca-tls namespace: cert-manager spec: isCA: true commonName: homelab.cloud-root-ca-tls secretName: homelab.cloud-root-ca-tls duration: 8760h # 1 year renewBefore: 720h # Certificates must be renewed some time before they expire (30 days) privateKey: algorithm: ECDSA size: 521 encoding: PKCS8 rotationPolicy: Always issuerRef: name: homelab.cloud-root-ca-issuer-selfsigned kind: ClusterIssuer group: cert-manager.ioTo know more about all the parameters shown above, check the cert-manager v1 api document here. Still, here you have an explanation of some particularities from this certificate declaration:
Many of the parameters are optional, and there are more that are not used here.
Here the API is also a cert-manager one. Be careful of the
apiVersionyou use. Cert-manager has several, each with its own API documentation.The
namespaceis the same one where cert-manager has been deployed. Therefore, both this certificate and its associated secret will be kept under thecert-managernamespace of your cluster, which is also where cert-manager looks for certificates by default.This certificate is not associated to any particular domain for security reasons. Root CA certificates are not meant to be exposed in any HTTPS communication, they are only used to sign other certificates.
The
durationdetermines how long the certificate lasts. Since this particular certificate is only used to sign others, you want it to last longer than the ones generated from it. On the other hand, it is also good to refresh it as frequently as possible. Therefore, a duration of one year is a good compromise, at least for a homelab environment.The
renewBeforeis about when to start the renewal of the certificate. This time period should be as short as possible, but always bearing in mind that the certificates derived from this one will also have to be renewed. In a small homelab setup, this period could be even shorter than the thirty days specified in the YAML above.The parameter
spec.isCAallows you to turn a certificate into a Certificate Authority. When the value istrue, you can use this certificate to sign other certificates issued by other issuers that rely on this CA’s secret.In the
spec.privateKeysection, be careful of:Configuring an algorithm supported by all current browsers. For instance, at the time of writing this, the algorithm
Ed25519is not supported neither by Firefox nor Chrome, nor any browser derived from them.Always having
rotationPolicyset asAlways. This makes cert-manager regenerate the certificate’s secret rather than reusing the current one. This policy about private key rotation is also described in the cert-manager documentation.
In the
spec.issuerRefyou specify the issuer of this certificate, in this case thehomelab.cloud-root-ca-issuer-selfsignedone you created in previous steps. Be careful of always also specifying itskind, in particular forClusterIssuertypes, so you know clearly what kind of issuer you are using with each certificate.
Declare another
ClusterIssuerin thehomelab.cloud-root-ca-issuer.cluster-issuer.cert-manager.yamlfile:# Cluster-wide issuer using root CA's secret apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: homelab.cloud-root-ca-issuer spec: ca: secretName: homelab.cloud-root-ca-tlsThis is a different cluster wide issuer that uses the root CA’s
homelab.cloud-root-ca-tlssecret to issue and sign other certificates. In particular, you will use this issuer only to issue certificates for intermediate CAs like the one you will declare next.
Intermediate CA
Prepare here the intermediate CA that will certify the apps or services you deploy in your cluster:
Create the following empty YAML files in the
resourcesdirectory:$ touch $HOME/k8sprjs/cert-manager/certificates/resources/{homelab.cloud-intm-ca01-tls.certificate,homelab.cloud-intm-ca01-issuer.cluster-issuer}.cert-manager.yamlDeclare a certificate for an intermediate CA in the
homelab.cloud-intm-ca01-tls.certificate.cert-manager.yaml:# Certificate for intermediate CA 01 apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: homelab.cloud-intm-ca01-tls namespace: cert-manager spec: isCA: true commonName: homelab.cloud-intm-ca01-tls secretName: homelab.cloud-intm-ca01-tls duration: 4380h # 6 months renewBefore: 360h # Certificates must be renewed some time before they expire (15 days) privateKey: algorithm: ECDSA size: 521 encoding: PKCS8 rotationPolicy: Always issuerRef: name: homelab.cloud-root-ca-issuer kind: ClusterIssuer group: cert-manager.ioThis certificate is like the one for the root CA issuer, but is meant for an intermediate CA issuer. Since this certificate’s secret will be the one used to issue and sign the certificates for the apps you will deploy in later chapters, it has a shorter
durationandrenewBeforetime periods. Also notice that this certificate’s name is numbered (01), hinting at the possibility of having more than one intermediate CA. And like the root CA’s certificate, see how this certificate is not attached to any particular domain.Declare in
homelab.cloud-intm-ca01-issuer.cluster-issuer.cert-manager.yamlan intermediate CA cluster issuer:# Cluster-wide issuer using intermediate CA 01's secret apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: homelab.cloud-intm-ca01-issuer spec: ca: secretName: homelab.cloud-intm-ca01-tlsThis is the intermediate CA cluster issuer you will use to issue and sign the “leaf” certificates for the apps and services you will deploy in later chapters. In this case, this issuer uses the corresponding secret of the intermediate CA 01’s certificate declared in the previous step.
Note
Cluster issuers can issue certificates in any namespace
The apps and services you will deploy in later chapters of this guide are going to run in other namespaces thancert-manager. This makes necessary the use of a cluster issuer to issue the certificates and their corresponding secrets, since secrets in Kubernetes are not shared among namespaces.
Kustomize manifest for the self-signed CAs project
After declaring all the cluster issuers and certificates, assemble them under one Kustomize manifest:
Create the
kustomization.yamlfile in thecertificatesfolder:$ touch $HOME/k8sprjs/cert-manager/certificates/kustomization.yamlEnter the Kustomize manifest for your whole CA structure in
kustomization.yaml:# Certificates deployment apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - resources/homelab.cloud-root-ca-issuer-selfsigned.cluster-issuer.cert-manager.yaml - resources/homelab.cloud-root-ca-tls.certificate.cert-manager.yaml - resources/homelab.cloud-root-ca-issuer.cluster-issuer.cert-manager.yaml - resources/homelab.cloud-intm-ca01-tls.certificate.cert-manager.yaml - resources/homelab.cloud-intm-ca01-issuer.cluster-issuer.cert-manager.yaml
Validating the Kustomize YAML output
To be sure that you got the Kustomize declaration right, redirect its kubectl YAML output to less or your text editor of choice:
$ kubectl kustomize $HOME/k8sprjs/cert-manager/certificates/ | lessThe output should look like this:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: homelab.cloud-intm-ca01-tls
namespace: cert-manager
spec:
commonName: homelab.cloud-intm-ca01-tls
duration: 4380h
isCA: true
issuerRef:
group: cert-manager.io
kind: ClusterIssuer
name: homelab.cloud-root-ca-issuer
privateKey:
algorithm: ECDSA
encoding: PKCS8
rotationPolicy: Always
size: 521
renewBefore: 360h
secretName: homelab.cloud-intm-ca01-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: homelab.cloud-root-ca-tls
namespace: cert-manager
spec:
commonName: homelab.cloud-root-ca-tls
duration: 8760h
isCA: true
issuerRef:
group: cert-manager.io
kind: ClusterIssuer
name: homelab.cloud-root-ca-issuer-selfsigned
privateKey:
algorithm: ECDSA
encoding: PKCS8
rotationPolicy: Always
size: 521
renewBefore: 720h
secretName: homelab.cloud-root-ca-tls
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: homelab.cloud-intm-ca01-issuer
spec:
ca:
secretName: homelab.cloud-intm-ca01-tls
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: homelab.cloud-root-ca-issuer
spec:
ca:
secretName: homelab.cloud-root-ca-tls
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: homelab.cloud-root-ca-issuer-selfsigned
spec:
selfSigned: {}Deploying the self-signed CAs
The Kustomize manifest is ready, so deploy it:
Apply the Kustomize project into your cluster:
$ kubectl apply -k $HOME/k8sprjs/cert-manager/certificatesConfirm that the resources have been deployed in the cluster:
$ kubectl -n kube-system get clusterissuer NAME READY AGE homelab.cloud-intm-ca01-issuer True 24s homelab.cloud-root-ca-issuer True 24s homelab.cloud-root-ca-issuer-selfsigned True 24s $ kubectl -n cert-manager get certificates NAME READY SECRET AGE homelab.cloud-intm-ca01-tls True homelab.cloud-intm-ca01-tls 60s homelab.cloud-root-ca-tls True homelab.cloud-root-ca-tls 59s $ kubectl -n cert-manager get secrets NAME TYPE DATA AGE cert-manager-webhook-ca Opaque 3 45m homelab.cloud-intm-ca01-tls kubernetes.io/tls 3 73s homelab.cloud-root-ca-tls kubernetes.io/tls 3 73sAs a final verification, use
kubectlto get a detailed description of your new issuers’ current status:$ kubectl describe ClusterIssuer Name: homelab.cloud-intm-ca01-issuer Namespace: Labels: <none> Annotations: <none> API Version: cert-manager.io/v1 Kind: ClusterIssuer Metadata: Creation Timestamp: 2025-10-10T10:33:47Z Generation: 1 Resource Version: 397675 UID: bd1632cb-6d45-4f4d-9d3a-1beb1fef5f47 Spec: Ca: Secret Name: homelab.cloud-intm-ca01-tls Status: Conditions: Last Transition Time: 2025-10-10T10:33:47Z Message: Signing CA verified Observed Generation: 1 Reason: KeyPairVerified Status: True Type: Ready Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal KeyPairVerified 5m14s (x2 over 5m14s) cert-manager-clusterissuers Signing CA verified Name: homelab.cloud-root-ca-issuer Namespace: Labels: <none> Annotations: <none> API Version: cert-manager.io/v1 Kind: ClusterIssuer Metadata: Creation Timestamp: 2025-10-10T10:33:47Z Generation: 1 Resource Version: 397679 UID: 7d64cd39-01a9-4481-941d-56e09f35b8d2 Spec: Ca: Secret Name: homelab.cloud-root-ca-tls Status: Conditions: Last Transition Time: 2025-10-10T10:33:47Z Message: Signing CA verified Observed Generation: 1 Reason: KeyPairVerified Status: True Type: Ready Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal KeyPairVerified 5m14s (x2 over 5m14s) cert-manager-clusterissuers Signing CA verified Name: homelab.cloud-root-ca-issuer-selfsigned Namespace: Labels: <none> Annotations: <none> API Version: cert-manager.io/v1 Kind: ClusterIssuer Metadata: Creation Timestamp: 2025-10-10T10:33:47Z Generation: 1 Resource Version: 397682 UID: d74b321f-dbdb-4b3c-a124-435542899a7e Spec: Self Signed: Status: Conditions: Last Transition Time: 2025-10-10T10:33:47Z Observed Generation: 1 Reason: IsReady Status: True Type: Ready Events: <none>The three issuers are ready, although the
homelab.cloud-intm-ca01-issuerandhomelab.cloud-root-ca-issuerones had initialization problems (reported asWarningevents) due probably to a delay in the creation of the secrets they use.
Checking your certificates with the cert-manager command line tool
Remember that the cert-manager command line tool can help you in handling your certificates. For instance, you can execute the following command to see the status of the root CA homelab.cloud-root-ca-tls certificate you have created before:
$ kubectl cert-manager status certificate -n cert-manager homelab.cloud-root-ca-tls
Name: homelab.cloud-root-ca-tls
Namespace: cert-manager
Created at: 2025-10-10T12:33:47+02:00
Conditions:
Ready: True, Reason: Ready, Message: Certificate is up to date and has not expired
DNS Names:
Events: <none>
Issuer:
Name: homelab.cloud-root-ca-issuer-selfsigned
Kind: ClusterIssuer
Conditions:
Ready: True, Reason: IsReady, Message:
Events: <none>
Secret:
Name: homelab.cloud-root-ca-tls
Issuer Country:
Issuer Organisation:
Issuer Common Name: homelab.cloud-root-ca-tls
Key Usage: Digital Signature, Key Encipherment, Cert Sign
Extended Key Usages:
Public Key Algorithm: ECDSA
Signature Algorithm: ECDSA-SHA512
Subject Key ID: 7156a59ffd7553cec1c9d424b959ef41fc5521ed
Authority Key ID:
Serial Number: 2e656bafa35257d9aba094782966b0e10868c232
Events: <none>
Not Before: 2025-10-10T12:33:47+02:00
Not After: 2026-10-10T12:33:47+02:00
Renewal Time: 2026-09-10T12:33:47+02:00
No CertificateRequest found for this CertificateCert-manager’s Kustomize project attached to this guide
You can find the Kustomize project for the cert-manager deployment in this folder:
Relevant system paths
Folders in kubectl client system
$HOME/bin$HOME/k8sprjs/cert-manager$HOME/k8sprjs/cert-manager/certificates$HOME/k8sprjs/cert-manager/certificates/resources$HOME/k8sprjs/cert-manager/deployment
Files in kubectl client system
$HOME/bin/cmctl$HOME/bin/kubectl-cert_manager$HOME/k8sprjs/cert-manager/certificates/kustomization.yaml$HOME/k8sprjs/cert-manager/certificates/resources/homelab.cloud-intm-ca01-issuer.cluster-issuer.cert-manager.yaml$HOME/k8sprjs/cert-manager/certificates/resources/homelab.cloud-intm-ca01-tls.certificate.cert-manager.yaml$HOME/k8sprjs/cert-manager/certificates/resources/homelab.cloud-root-ca-issuer-selfsigned.cluster-issuer.cert-manager.yaml$HOME/k8sprjs/cert-manager/certificates/resources/homelab.cloud-root-ca-issuer.cluster-issuer.cert-manager.yaml$HOME/k8sprjs/cert-manager/certificates/resources/homelab.cloud-root-ca-tls.certificate.cert-manager.yaml$HOME/k8sprjs/cert-manager/deployment/kustomization.yaml
References
cert-manager
About setting up cert-manager
- Support Tools. Deep Dive into cert-manager and Cluster Issuers in Kubernetes
- Some Web. Setting up HTTPS with cert-manager (self-signed, LetsEncrypt) in kubernetes
- Paul Czarkowski. Creating Self Signed Certificates on Kubernetes
- Zachi Nachshon’s Blog. Install Certificate Manager Controller in Kubernetes
- Theodo. How to configure Traefik on Kubernetes with Cert-manager?
- StackOverflow. PKCS#1 and PKCS#8 format for RSA private key