Replacing Manual Certificate Management with Infrastructure as Code

For decades, the lifecycle of an SSL/TLS certificate followed a predictable, manual rhythm. An IT administrator would generate a Certificate Signing Request (CSR) on a server, paste it into a vendor's...

Tim Henrich
September 12, 2026
5 min read
26 views

Replacing Manual Certificate Management with Infrastructure as Code

For decades, the lifecycle of an SSL/TLS certificate followed a predictable, manual rhythm. An IT administrator would generate a Certificate Signing Request (CSR) on a server, paste it into a vendor's web portal, pay an invoice, download the resulting certificate, and manually install it. These certificates were treated like "pets"—purchased for three-year lifespans, carefully named, and individually maintained.

Today, that operational model is entirely broken. Machine identities now vastly outnumber human identities. Microservices, containers, and ephemeral cloud instances require cryptographic identities that may only live for a few hours. To survive in modern environments, certificates must be treated as "cattle"—ephemeral, highly automated, and defined entirely as code.

Transitioning to Infrastructure as Code (IaC) for certificate management is no longer just an optimization strategy for DevOps teams. It is a strict prerequisite for maintaining uptime, achieving Zero Trust Architecture (ZTA), and preparing for sweeping industry mandates.

The Forcing Functions Behind Code-Driven Certificates

Several converging industry trends have made manual certificate management mathematically and logistically impossible.

The Shrinking Public TLS Lifespan

Google’s stated intention to reduce the maximum validity period of public TLS certificates from 398 days to just 90 days is the most immediate catalyst. When certificates expire every three months, tracking renewals in spreadsheets or legacy ticketing systems guarantees an eventual outage. At this frequency, the issuance, validation, deployment, and rotation of certificates must happen without human intervention.

The Explosion of mTLS in Service Meshes

The adoption of Zero Trust Architecture mandates that all internal network traffic be authenticated and encrypted. In service meshes like Istio or Linkerd, this is achieved through mutual TLS (mTLS). Every single pod and microservice requires its own certificate. These internal certificates are often configured to expire in 24 hours or less to limit the blast radius of a compromised key. Provisioning thousands of daily certificates requires a robust, API-driven pipeline.

Post-Quantum Cryptography (PQC) and Crypto-Agility

In August 2024, NIST finalized the first set of Post-Quantum Cryptography standards (FIPS 203, 204, and 205). Over the next few years, organizations will need to migrate away from classical algorithms like RSA and ECC to quantum-resistant algorithms. Organizations managing certificates via IaC achieve "crypto-agility." When the time comes to swap algorithms across ten thousand endpoints, they can simply update a Terraform module or a Helm chart variable, rather than manually re-keying individual servers.

The Plaintext State File Trap in Terraform

When engineering teams first attempt to automate certificates using Terraform or OpenTofu, they frequently stumble into a critical security vulnerability: secret sprawl within the state file.

It is tempting to use the hashicorp/tls provider to generate a private key and a CSR directly within the infrastructure code:

# ANTIPATTERN: Do not do this in production
resource "tls_private_key" "example" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

The issue here is fundamental to how Terraform operates. The resulting private key is stored in plaintext inside the terraform.tfstate file. If an attacker gains read access to your state file—whether through a misconfigured S3 bucket, compromised CI/CD pipeline logs, or overly permissive RBAC—they instantly possess the private key, rendering the certificate completely compromised.

The Solution: Decouple Key Generation from IaC

To fix this, you must use IaC to provision the infrastructure of the certificate, while relying on a dedicated secrets manager or cloud provider to handle the cryptographic material. The private key should never touch your local machine or your CI runner.

When using a cloud provider like AWS, best practice dictates utilizing AWS Certificate Manager (ACM) combined with Route53 for automated ACME DNS-01 validation. In this workflow, AWS generates and stores the private key internally; Terraform simply orchestrates the validation.

# Define the desired state of the certificate
resource "aws_acm_certificate" "api_cert" {
  domain_name       = "api.example.com"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

# Automate the DNS-01 challenge by creating the required Route53 records
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.api_cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.example.zone_id
}

# Tell Terraform to wait for the certificate to be issued before proceeding
resource "aws_acm_certificate_validation" "api_cert_ready" {
  certificate_arn         = aws_acm_certificate.api_cert.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

In this model, the private key is securely generated inside AWS ACM and never exposed to Terraform state.

Cloud-Native Automation with Kubernetes cert-manager

For containerized environments, the industry standard for declarative certificate management is cert-manager. Rather than writing procedural scripts to request and renew certificates, cert-manager extends the Kubernetes API using Custom Resource Definitions (CRDs).

You define an Issuer (the Certificate Authority) and a Certificate (the desired cryptographic identity). The operator handles the entire lifecycle asynchronously.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: security@example.com
    privateKeySecretRef:
      name: letsencrypt-production-account-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-example-com-tls
  namespace: production
spec:
  secretName: api-example-com-tls-secret
  duration: 2160h # 90 days
  renewBefore: 360h # 15 days
  issuerRef:
    name: letsencrypt-production
    kind: ClusterIssuer
  dnsNames:
  - api.example.com

Because these YAML manifests are stored in Git, they benefit from standard GitOps workflows. If a certificate is accidentally deleted from the cluster, the continuous delivery tool (like ArgoCD or Flux) will immediately detect the drift and recreate the Certificate resource, prompting cert-manager to fetch a new one.

Surviving CI/CD Rate Limits in Ephemeral Environments

As teams mature their IaC practices, they often spin up ephemeral, full-stack environments for every pull request. A common pitfall is configuring these ephemeral environments to request real certificates from Let's Encrypt.

Let's Encrypt enforces strict rate limits—typically 50 certificates per registered domain per week. A busy CI/CD pipeline will exhaust this limit in hours, causing subsequent infrastructure deployments to fail and blocking the entire engineering organization.

The Solution: Environment-Aware CA Routing

Your infrastructure code must be context-aware. Use variables to route certificate requests to different Certificate Authorities based on the environment:

  1. Production: Uses Let's Encrypt Production or a commercial CA (DigiCert, Sectigo).
  2. **

Share This Insight

Related Posts