Skip to content
StackPractices
beginner By Mathias Paulenko

SSL Certificate Renewal Template

A template for tracking SSL certificate expiration and renewal workflows.

Topics: devops

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Overview

SSL/TLS certificates expire. When they do, browsers block your site, APIs fail, and users see scary security warnings. Most teams discover an expired certificate when a customer complains. This template creates a repeatable renewal workflow with tracking, validation checks, and rollback steps so you never miss an expiration again.

When to Use

Use this resource when:

  • You manage multiple domains, subdomains, or wildcard certificates across environments
  • Your team has missed a certificate expiration in the past
  • You are migrating from manual renewal to automated certificate management

Solution

# SSL Certificate Renewal Tracker: `<Domain>`

## 1. Certificate Inventory

| Domain / Subdomain | Provider | Type | Expiration | Auto-Renew | Owner | Last Verified |
|--------------------|----------|------|------------|------------|-------|---------------|
| `example.com` | Let's Encrypt | DV | `YYYY-MM-DD` | Yes | `@sre-team` | `YYYY-MM-DD` |
| `*.api.example.com` | DigiCert | Wildcard OV | `YYYY-MM-DD` | No | `@platform-team` | `YYYY-MM-DD` |
| `internal.example.com` | Internal CA | Enterprise | `YYYY-MM-DD` | N/A | `@it-team` | `YYYY-MM-DD` |

## 2. Renewal Timeline

| Phase | Trigger | Action | Owner | Deadline |
|-------|---------|--------|-------|----------|
| Alert | 30 days before expiry | Create renewal ticket | `@sre-team` | — |
| Prep | 14 days before expiry | Verify DNS control + generate CSR | `@sre-team` | — |
| Request | 10 days before expiry | Submit certificate request | `@sre-team` | — |
| Install | 7 days before expiry | Deploy to load balancers / CDN | `@sre-team` | — |
| Validate | 5 days before expiry | Run validation checks (see below) | `@sre-team` | — |
| Monitor | 48 hours after install | Watch for mixed-content or handshake errors | `@on-call` | — |

## 3. Validation Checklist

- [ ] Certificate chain is complete (leaf + intermediates + root)
- [ ] Domain name matches exactly (no SAN omissions)
- [ ] Expiration date is beyond the expected renewal window
- [ ] TLS 1.2+ handshake succeeds from external probe
- [ ] OCSP responder is reachable and returns valid status
- [ ] No mixed-content warnings on primary pages
- [ ] Mobile apps and third-party integrations accept the new certificate
- [ ] Old certificate revoked (if applicable) after validation

## 4. Rollback Plan

| Condition | Action | Time to Complete |
|-----------|--------|-----------------|
| New cert causes handshake failures | Re-deploy previous certificate from backup | 5 minutes |
| Chain incomplete | Re-bundle with correct intermediate CA | 10 minutes |
| SAN missing | Re-issue with corrected CSR | 2–24 hours (provider dependent) |

## 5. Automation Notes

| Tool | Renewal Method | Validation | Deployment Target |
|------|---------------|------------|-------------------|
| certbot | ACME challenge | HTTP-01 / DNS-01 | Local web server |
| cert-manager (Kubernetes) | ACME / Vault | DNS-01 / HTTP-01 | Kubernetes secret → Ingress |
| AWS ACM | Managed | DNS validation | ALB / CloudFront |
| Azure Key Vault | Managed / imported | DNS / email | Application Gateway / CDN |

Explanation

The template separates inventory (what certificates you have) from workflow (how you renew them). The inventory prevents certificates from being forgotten on edge services (CDNs, internal APIs, mobile gateways). The workflow enforces a buffer: if anything goes wrong during renewal, you have days to fix it before expiry. The validation checklist catches the most common post-deployment issues—missing intermediate certificates and incomplete SANs—before users do.

Variants

EnvironmentCertificate SourceRenewal StrategyNotes
Public webLet’s Encrypt (ACME)Automated 60-day cycle with certbot or cert-managerFree, short-lived (90 days)
Enterprise SaaSDigiCert / SectigoAnnual purchase with manual approval for OV/EVLonger validity, higher trust
Internal servicesInternal PKI / VaultAuto-renewal via Vault PKI engineFull control, requires trust distribution
IoT / embeddedDevice-attested certsFactory provisioning + OTA updatesLimited validation options

What Works

  1. Set calendar alerts at 30, 14, and 7 days before expiration—even for “auto-renewing” certificates
  2. Store certificate backups (private key + chain) in a secrets manager, not on a single engineer’s laptop
  3. Test the renewal process on staging before production; ACME challenges can fail due to DNS or firewall changes
  4. Use wildcard certificates sparingly; they reduce management overhead but increase blast radius if compromised
  5. Document the exact command or pipeline used for renewal so anyone on-call can execute it

Common Mistakes

  1. Relying on auto-renewal without monitoring whether it actually succeeded
  2. Forgetting to update certificate on CDN or WAF after renewing on the origin server
  3. Missing intermediate certificates in the bundle, causing “untrusted” errors on some clients
  4. Not including all required SANs in the CSR (e.g., www. and apex domain)
  5. Letting the old certificate expire before validating the new one on all endpoints

Troubleshooting

  • Pipeline fails silently: enable verbose logging and store pipeline artifacts between stages so you can inspect the exact state that failed.
  • Container crashes on startup: check that environment variables, secrets, and config files are mounted correctly. Read the first 50 lines of logs before scaling replicas.
  • Deployment rolls back repeatedly: verify health checks, resource limits, and startup probes. A failing readiness probe is a common cause of rolling restarts.
  • Slow CI builds: cache dependencies and docker layers. Split large test suites into parallel jobs to reduce wall-clock time.
  • Drift between environments: use infrastructure-as-code and immutable artifacts. Compare deployed versions with the declared source of truth before debugging behavior differences.

FAQ

How do I handle wildcard certificates?

Wildcard certificates (*.example.com) cover all subdomains but cannot cover the apex domain (example.com) unless explicitly included as a SAN. For ACME wildcard renewal, you must use DNS-01 validation, which requires API access to your DNS provider. Keep wildcard scope narrow—do not use a single wildcard across production and staging.

What is the difference between DV, OV, and EV certificates?

DV (Domain Validated) proves you control the domain. OV (Organization Validated) adds verified company identity. EV (Extended Validation) shows the company name in the browser bar and requires the most rigorous vetting. For most APIs and internal services, DV is sufficient. Customer-facing SaaS may benefit from OV for brand trust.

Should I use a CDN-managed certificate or bring my own?

CDN-managed certificates (AWS ACM, Cloudflare Origin CA) simplify deployment and auto-renewal but lock you to that provider. Bring-your-own certificates offer portability but require you to handle renewal and deployment. For multi-cloud architectures, use a central secrets manager (HashiCorp Vault, AWS Secrets Manager) and push certificates to each edge during deployment.

Advanced Solutions

cert-manager for Kubernetes with Let’s Encrypt

Automate certificate issuance and renewal in Kubernetes using cert-manager with DNS-01 challenge:

# Install cert-manager CRDs and configure ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: platform-team@company.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - dns01:
          cloudflare:
            email: platform-team@company.com
            apiKeySecretRef:
              name: cloudflare-api-key
              key: api-key
---
# Issue a certificate for the ingress
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-tls
  namespace: production
spec:
  secretName: api-tls-secret
  duration: 2160h    # 90 days
  renewBefore: 360h  # Renew 15 days before expiry
  dnsNames:
    - api.example.com
    - "*.api.example.com"
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
---
# Reference in Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-secret

HashiCorp Vault PKI engine for internal certificates

Set up a private CA using Vault PKI secrets engine for internal services:

# Enable PKI engine
vault secrets enable -path=pki pki

# Configure max lease duration
vault secrets tune -max-lease-ttl=87600h pki

# Generate root CA
vault write pki/root/generate/internal \
    common_name="Internal CA" \
    ttl=87600h

# Configure URLs for CRL and issuing
vault write pki/config/urls \
    issuing_certificates="https://vault.internal:8200/v1/pki/ca" \
    crl_distribution_points="https://vault.internal:8200/v1/pki/crl"

# Create a role for issuing certificates
vault write pki/roles/internal-service \
    allowed_domains="internal.company.com" \
    allow_subdomains=true \
    max_ttl="720h" \
    key_type="rsa" \
    key_bits=2048

# Issue a certificate
vault write pki/issue/internal-service \
    common_name="api.internal.company.com" \
    ttl="24h"

Certificate expiration monitoring script

A bash script to check all certificates in a Kubernetes cluster and alert on upcoming expirations:

#!/bin/bash
set -euo pipefail

DAYS_THRESHOLD=30
ALERT_EMAIL="platform-team@company.com"

echo "Checking TLS certificates across all namespaces..."

kubectl get secrets --all-namespaces -o json | \
  jq -r '.items[] | select(.type=="kubernetes.io/tls") | "\(.metadata.namespace) \(.metadata.name) \(.data."tls.crt")"' | \
  while read -r namespace secret_name cert_b64; do
    cert_pem=$(echo "$cert_b64" | base64 -d)
    expiry_date=$(echo "$cert_pem" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
    if [ -z "$expiry_date" ]; then
      continue
    fi

    expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$expiry_date" +%s 2>/dev/null)
    now_epoch=$(date +%s)
    days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

    if [ "$days_left" -lt "$DAYS_THRESHOLD" ]; then
      echo "WARNING: $namespace/$secret_name expires in $days_left days ($expiry_date)"
    fi
  done

Additional Best Practices

  1. Use certificate transparency monitoring. Subscribe to CT logs for your domains to detect unauthorized certificate issuance. Set up alerts when a new certificate is issued for a domain you own:
# Check CT logs for your domain using curl
curl -s "https://crt.sh/?q=%25.example.com&output=json" | \
  jq '.[] | select(.not_before > "2026-01-01") | {issuer_name, common_name, not_before}'
  1. Implement HSTS preload for production domains. Once you have reliable certificate automation, enable HTTP Strict Transport Security to prevent downgrade attacks:
# nginx configuration
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Additional Common Mistakes

  1. Using TLS 1.0 or 1.1 after certificate renewal. Renewing a certificate does not update your TLS configuration. Explicitly disable old protocols during renewal to avoid compliance failures:
# Disable TLS 1.0 and 1.1
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
  1. Not updating OCSP stapling configuration. After renewal, OCSP stapling may serve stale data from the old certificate. Restart the web server or clear the OCSP cache:
# Restart nginx to refresh OCSP stapling
nginx -t && systemctl restart nginx

Additional Frequently Asked Questions

How do I automate certificate renewal for multiple domains?

Use cert-manager in Kubernetes or a central ACME client (certbot, acme.sh) with a cron job. For non-Kubernetes environments, certbot with DNS-01 challenge and a DNS provider plugin handles wildcard and multi-domain certificates automatically:

# certbot wildcard renewal with Cloudflare DNS
certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
  -d "*.example.com" \
  -d "example.com" \
  --non-interactive \
  --agree-tos \
  --email platform-team@company.com

What is OCSP stapling and why does it matter?

OCSP stapling allows the server to include a signed OCSP response from the CA in the TLS handshake, so the client does not need to contact the CA’s OCSP responder separately. This improves performance and privacy. Enable it in your web server configuration and ensure the OCSP responder is reachable during renewal.

Common Production Pitfalls

  • Leaving required fields blank or using vague one-word answers.
  • Filling the document once and never updating it after scope or decisions change.
  • Storing the document where the team does not look during incidents or reviews.
  • Not assigning an owner, due date, or review cadence.
  • Copying boilerplate without removing sections that do not apply.
  • Skipping version control, which makes rollback and accountability impossible.
  • Failing to link the document to related decisions or follow-up actions.
  • Avoiding quarterly reviews that would retire stale or unused sections.