Introduction

This guide explains how to configure Let’s Encrypt certificates using CertManager in a Kubernetes cluster managed with GitOps using Flux. We’ll use Cloudflare for DNS validation. By the end, you’ll have automated certificate issuance and management, improving security and ease of use.

Prerequisites

Before proceeding, ensure you have the following:

  • Kubernetes Cluster: A running cluster.
  • Flux: Installed and configured for GitOps.
  • Cloudflare Account: Access with API token privileges.

Overview of Steps

  1. Set up the namespace and Helm repository for cert-manager.
  2. Configure Cloudflare API tokens.
  3. Create staging and production issuers.
  4. Deploy certificates.
  5. Verify and troubleshoot the setup.

Here is the repository tree that you likely have at this moment with - new files to deploy in orange - files to update in green

..
├── apps
│   └── debugpod
│       └── base
│           ├── debugpod.yaml
│           └── kustomization.yaml
├── clusters
│   └── production
│       ├── apps
│       │   └── kustomization.yaml
│       ├── flux-system
│       │   ├── cert-manager.yaml
│       │   ├── gotk-components.yaml
│       │   ├── gotk-sync.yaml
│       │   ├── kustomization.yaml
│       │   ├── pull-secrets.yaml
│       │   └── sources
│       │       ├── cert-manager-helmrelease.yaml
│       │       ├── cert-manager-namespace.yaml
│       │       ├── jetstack-helmrepository.yaml
│       │       └── kustomization.yaml
│       └── kustomization.yaml
├── infrastructure
│   ├── monitoring
│   │   ├── grafana
│   │   └── prometheus
│   ├── networking
│   │   └── ingress-nginx
│   └── security
│       ├── cert-manager
│       │   ├── kustomization.yaml
│       │   ├── letsencrypt-cloudflare-api-token-secret.yaml
│       │   ├── letsencrypt-production-certificate.yaml
│       │   ├── letsencrypt-production-clusterissuer.yaml
│       │   ├── letsencrypt-staging-certificate.yaml
│       │   └── letsencrypt-staging-clusterissuer.yaml
│       └── pull-secrets
│           ├── docker-hub.yaml
│           └── kustomization.yaml
├── kubeconfig
├── policies
│   ├── gatekeeper
│   │   ├── constraints
│   │   └── templates
│   └── kyverno
│       ├── policies
│       └── rules
├── README.md
├── secrets.yaml
└── talosconfig

Step 1: Configure Cert-Manager Namespace and Helm Repository

Create Namespace

Save the following YAML to clusters/production/flux-system/sources/cert-manager-namespace.yaml:

# File: clusters/production/flux-system/sources/cert-manager-namespace.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager

Add Helm Repository

Save the following YAML to clusters/production/flux-system/sources/jetstack-helmrepository.yaml:

# File: clusters/production/flux-system/sources/jetstack-helmrepository.yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: jetstack
  namespace: flux-system
spec:
  interval: 1h
  url: https://charts.jetstack.io

Install Cert-Manager with HelmRelease

Save the following YAML to clusters/production/flux-system/sources/cert-manager-helmrelease.yaml:

# File: clusters/production/flux-system/sources/cert-manager-helmrelease.yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  interval: 1h
  releaseName: cert-manager
  targetNamespace: cert-manager
  chart:
    spec:
      chart: cert-manager
      sourceRef:
        kind: HelmRepository
        name: jetstack
        namespace: flux-system
      version: 1.16.2
  values:
    installCRDs: true
  install:
    crds: CreateReplace
    createNamespace: true

Refer Helm files

in clusters/production/flux-system/sources/kustomization.yaml

# File: clusters/production/flux-system/sources/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - cert-manager-namespace.yaml
  - jetstack-helmrepository.yaml
  - cert-manager-helmrelease.yaml

Step 2: Configure Cloudflare API Token

Generate a Cloudflare API token with DNS edit permissions.

Save the following YAML to infrastructure/security/cert-manager/letsencrypt-cloudflare-api-token-secret.yaml:

# File: infrastructure/security/cert-manager/letsencrypt-cloudflare-api-token-secret.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: letsencrypt-cloudflare-api-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-token: <your-cloudflare-api-token>

Step 3: Create ClusterIssuers

Staging Issuer

Save the following YAML to infrastructure/security/cert-manager/letsencrypt-staging-clusterissuer.yaml:

# File: infrastructure/security/cert-manager/letsencrypt-staging-clusterissuer.yaml
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: <your--CF-account-email>
    privateKeySecretRef:
      name: letsencrypt-staging-issuer-secret
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: letsencrypt-cloudflare-api-token-secret
            key: api-token

Production Issuer

Save the following YAML to infrastructure/security/cert-manager/letsencrypt-production-clusterissuer.yaml:

# File: infrastructure/security/cert-manager/letsencrypt-production-clusterissuer.yaml
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <your_CF_registered@email@here>
    privateKeySecretRef:
      name: letsencrypt-production-issuer-secret
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: letsencrypt-cloudflare-api-token-secret
            key: api-token

Step 4: Create Certificates

Staging Certificate

Save the following YAML to infrastructure/security/cert-manager/letsencrypt-staging-certificate.yaml:

# File: infrastructure/security/cert-manager/letsencrypt-staging-certificate.yaml
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: letsencrypt-staging-certificate
  namespace: default
spec:
  dnsNames:
  - <endpoint>.<your_domain.com>
  secretName: letsencrypt-staging-cert-tls
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-staging

Production Certificate

Save the following YAML to infrastructure/security/cert-manager/letsencrypt-production-certificate.yaml:

# File: infrastructure/security/cert-manager/letsencrypt-production-certificate.yaml
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: letsencrypt-production-certificate
  namespace: default
spec:
  dnsNames:
  - <endpoint>.<your_domain.com>
  secretName: letsencrypt-production-cert-tls
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-production
  commonName: <endpoint>.<your_domain.com>
  renewBefore: 720h

Optional. Certificates Clean-Up

Cron Job to clean-up expired certificates can be created with

infrastructure/security/cert-manager/cronjob-old-certificate-removal.yaml:

# File: infrastructure/security/cert-manager/cronjob-old-certificate-removal.yaml
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cert-cleanup
  namespace: default
spec:
  schedule: "0 3 * * *" # Runs daily at 3 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cert-cleanup
            image: bitnami/kubectl
            command:
            - /bin/sh
            - -c
            - |
              kubectl delete secret $(kubectl get secrets --no-headers | grep 'old-certificate' | awk '{print $1}')
          restartPolicy: OnFailure

Refer cert-issuer files

in infrastructure/security/cert-manager/kustomization.yaml:

# File: infrastructure/security/cert-manager/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cert-manager
# Include all YAML resources in this directory
resources:
  - letsencrypt-cloudflare-api-token-secret.yaml
  - letsencrypt-production-certificate.yaml
  - letsencrypt-production-clusterissuer.yaml
  - letsencrypt-staging-certificate.yaml
  - letsencrypt-staging-clusterissuer.yaml
  - cronjob-old-certificate-removal.yaml  # Optional

Update FLux-System

Create clusters/production/flux-system/cert-manager.yaml:

# File: clusters/production/flux-system/cert-manager.yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  interval: 10m
  path: ./infrastructure/security/cert-manager
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
    namespace: flux-system

And refer it as well as sources folder in clusters/production/flux-system/kustomization.yaml:

# File: clusters/production/flux-system/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
- pull-secrets.yaml
- sources
- cert-manager.yaml

Commit and enjoy…or troubleshoot

git add -A 
git commit -m "Certificates management" 
git push origin main 
flux reconcile kustomization flux-system --namespace flux-system

Check what has been deployed:

# repo
kubectl get helmrepository --all-namespaces

# release
kubectl get helmchart --all-namespaces

# cluster issuer
kubectl get clusterissuers

# pods for cert-manager
kubectl get pods --all-namespaces

# certificates
kubectl get certificate --all-namespaces