GitOps for Certificates: From Manual Chaos to Automated Order

It’s the alert no one wants to see: "SSL_ERROR_EXPIRED". It means your service is down, customer trust is eroding, and the clock is ticking on a costly outage. A **2024 report by the Ponemon Institute...

Tim Henrich
January 15, 2026
8 min read
28 views

GitOps for Certificates: From Manual Chaos to Automated Order

It’s the alert no one wants to see: "SSL_ERROR_EXPIRED". It means your service is down, customer trust is eroding, and the clock is ticking on a costly outage. A 2024 report by the Ponemon Institute revealed that 55% of organizations suffered at least four certificate-related outages in the last two years, with each incident costing hundreds of thousands of dollars per hour. In an era of 90-day certificate lifespans and sprawling microservices, manual management isn't just inefficient—it's a critical business risk.

The solution lies in applying the same principles that revolutionized infrastructure management to certificate lifecycle management (CLM): GitOps. By treating your certificate configurations as code, you can transform a reactive, error-prone process into a proactive, automated, and auditable system.

This guide will walk you through the why and how of building a robust GitOps workflow for certificate management. We'll cover the core components, a practical implementation guide, and best practices for achieving a state of cryptographic zen, where certificate outages become a relic of the past.

What is GitOps and Why Apply it to Certificates?

GitOps is an operational framework that takes DevOps best practices used for application development—version control, collaboration, CI/CD—and applies them to infrastructure automation. At its core, GitOps has a few key principles:

  1. Declarative: The entire desired state of your system is described declaratively in a Git repository. For certificates, this means defining issuers, certificate requests, and renewal policies in YAML files.
  2. Versioned and Immutable: Git is the single source of truth. Every change is a commit, giving you a complete, auditable history of who changed what, when, and why.
  3. Pulled Automatically: In-cluster agents, known as GitOps controllers, continuously pull the desired state from Git and apply it to the live environment.
  4. Continuously Reconciled: The controllers constantly monitor the live state to ensure it matches the source of truth in Git. If there's any drift, it's automatically corrected.

Applying this to CLM is a natural fit. Instead of manually running openssl commands or clicking through a CA's web UI, you define a Certificate resource in a YAML file. You specify the domain names, the issuer, and the renewal policy. The rest is handled automatically. This approach turns certificate management from a stressful, manual chore into a reliable, self-healing system.

The Anatomy of a GitOps CLM Workflow

Building a GitOps-powered CLM system for Kubernetes involves combining a few key open-source tools into a cohesive workflow. Each component plays a critical role in maintaining the desired state of your certificates.

The Core Components

  • Git Repository: Your single source of truth. This is where you store all declarative configurations, including your Kubernetes manifests for issuers and certificates. Popular choices include GitHub, GitLab, and Bitbucket.
  • GitOps Controller: The engine that automates synchronization. It runs inside your cluster, watches your Git repository, and applies any changes. The two leading tools are Flux and Argo CD.
  • Certificate Manager: The in-cluster operator that handles the entire certificate lifecycle. It watches for Certificate resources and takes care of generating private keys, creating certificate signing requests (CSRs), communicating with CAs like Let's Encrypt via the ACME protocol, and renewing certificates before they expire. cert-manager is the de facto standard in the Kubernetes ecosystem.
  • Secret Management: A critical piece of the security puzzle. You should never commit private keys directly to Git. GitOps-native secret management tools encrypt secrets before they are committed, ensuring that only the in-cluster controller can decrypt them. The most common solutions are Bitnami Sealed Secrets and the External Secrets Operator (ESO).

A Step-by-Step Implementation Guide

Let's walk through a practical example of issuing a TLS certificate for a web application using Flux and cert-manager.

Prerequisites:
* A running Kubernetes cluster.
* Flux installed and configured to sync with your Git repository.
* cert-manager installed in your cluster.

Step 1: Define a ClusterIssuer in Git

First, we need to tell cert-manager how to obtain certificates. We'll define a ClusterIssuer, a cluster-scoped resource that represents a certificate authority. This example uses Let's Encrypt's staging environment for safe testing.

Create a file named issuers/letsencrypt-staging.yaml in your Git repository:

# issuers/letsencrypt-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: your-email@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx

Commit and push this file to your Git repository. Flux will detect the new file and apply it to the cluster, creating the ClusterIssuer resource. cert-manager's controller will then see this resource and provision the necessary ACME account key.

Step 2: Define a Certificate Resource for Your Application

Now, let's request a certificate for myapp.example.com. We define a Certificate resource in the application's namespace.

Create a file named apps/myapp/certificate.yaml in your repository:

# apps/myapp/certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myapp-tls
  namespace: myapp
spec:
  # The name of the secret to store the TLS certificate and private key
  secretName: myapp-tls-secret
  # Common Name and DNS names for the certificate
  commonName: myapp.example.com
  dnsNames:
  - myapp.example.com
  # Reference to our ClusterIssuer
  issuerRef:
    name: letsencrypt-staging
    kind: ClusterIssuer
  # Configure renewal settings
  renewBefore: 360h # 15 days

This manifest tells cert-manager to issue a certificate for myapp.example.com using the letsencrypt-staging issuer. The resulting key and certificate will be stored in a Kubernetes Secret named myapp-tls-secret. We've also specified renewBefore: 360h, instructing cert-manager to begin the renewal process 15 days before expiration.

Step 3: Commit, Push, and Watch the Magic Happen

Commit and push the certificate.yaml file. Here’s what happens next:

  1. Flux Syncs: Flux pulls the new commit from Git and applies the Certificate manifest to the myapp namespace.
  2. cert-manager Acts: cert-manager's controller detects the new Certificate resource.
  3. Challenge and Issuance: It creates the necessary resources (Pods, Services, Ingresses) to solve the ACME HTTP-01 challenge, proves ownership of the domain to Let's Encrypt, and receives the signed certificate.
  4. Secret Creation: It creates the myapp-tls-secret containing the tls.crt, tls.key, and ca.crt.
  5. Automatic Renewal: cert-manager continuously monitors the certificate's expiration. 15 days before it expires, it will automatically repeat the process to renew it, ensuring your service never goes down.

Your application's Ingress or Gateway can now reference myapp-tls-secret to terminate TLS traffic securely, all without any manual intervention.

Solving Critical Security and Compliance Challenges

Beyond preventing outages, a GitOps approach to CLM directly addresses deep-seated security and compliance challenges that plague traditional environments.

Eliminating Secret Sprawl with Encrypted Secrets

The most critical rule of GitOps is never commit unencrypted secrets to Git. Private keys for your certificates are highly sensitive credentials. The solution is to use an operator that decrypts secrets at deploy time.

  • Bitnami Sealed Secrets: With this model, you use a CLI tool (kubeseal) and a public key from the in-cluster controller to encrypt a standard Kubernetes Secret manifest. The resulting SealedSecret custom resource is safe to commit to Git. Only the controller with the corresponding private key can decrypt it and create the Secret in the cluster.

  • External Secrets Operator (ESO): This is often preferred in larger organizations. Instead of storing an encrypted secret in Git, you store a manifest that references a secret in a dedicated secrets manager like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault. The ESO fetches the secret from the external store and syncs it into a native Kubernetes Secret. This keeps your Git repository free of any secret material, encrypted or not.

Enforcing Policy as Code for Governance

How do you prevent a developer from requesting a certificate for a non-company domain or using a weak key algorithm? With GitOps, you can use policy-as-code engines to enforce rules automatically.

Tools like Kyverno or OPA Gatekeeper act as admission controllers in your Kubernetes cluster. You can write policies that validate or mutate resources before they are created.

For example, here is a simple Kyverno ClusterPolicy that ensures all Certificate resources use an approved ClusterIssuer and have a minimum key size:

# policies/enforce-certificate-standards.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: enforce-certificate-standards
spec:
  validationFailureAction: Enforce
  rules:
  - name: validate-issuer
    match:
      any:
      - resources:
          kinds:
          - Certificate
    validate:
      message: "Certificates must use an approved ClusterIssuer (letsencrypt-prod)."
      pattern:
        spec:
          issuerRef:
            name: "letsencrypt-prod"
  - name: validate-key-size
    match:
      any:
      - resources:
          kinds:
          - Certificate
    validate:
      message: "RSA key size must be 2048 or greater."
      pattern:
        spec:
          privateKey:
            algorithm: "RSA"
            size: ">=2048"

Committing this policy to Git ensures that any non-compliant Certificate manifest will be rejected by the API server, providing a powerful, automated guardrail.

Achieving Bulletproof Auditability

For compliance standards like SOC 2, PCI DSS, or HIPAA, proving who authorized a certificate and when it was deployed is essential. Git provides this out of the box. The immutable Git log is a perfect audit trail. Every certificate issuance, renewal, or configuration change is tied to:

  • A commit hash
  • An author
    *

Share This Insight

Related Posts