SSL/TLS Certificate Management Automation: Let's Encrypt, cert-manager, Wildcards, and Monitoring

Published 15 min read
ssltlsletsencryptcert-managerhstscertificatessecurity

Certificate expiration is the most preventable cause of production outages, yet it causes roughly 25% of all TLS-related incidents according to the 2025 Venafi Machine Identity Report. Automated certificate management eliminates manual renewal, reduces outage risk to near zero, and handles the complexity of wildcard certificates and multi-domain SANs. This guide covers Let's Encrypt automation on VPS, cert-manager on Kubernetes, wildcard certificates, HSTS deployment, and certificate monitoring.

---

Let's Encrypt Automation on VPS

Let's Encrypt provides free, automated TLS certificates valid for 90 days. Certbot handles the ACME protocol for issuance and automatic renewal.

<h1>Install Certbot with nginx plugin</h1>

sudo apt install -y certbot python3-certbot-nginx

<h1>Obtain certificate for single domain</h1>

sudo certbot --nginx -d example.com -d www.example.com \

--non-interactive --agree-tos \

--email [email protected]

<h1>Verify automatic renewal is configured</h1>

sudo certbot renew --dry-run

<h1>Check renewal timer</h1>

systemctl list-timers | grep certbot

Certbot automatically adds a systemd timer that runs twice daily and renews certificates within 30 days of expiration. The nginx plugin handles reconfiguration and reload automatically.

Certbot Renewal Hooks

<h1>/etc/letsencrypt/renewal-hooks/deploy/restart-services.sh</h1>

#!/bin/bash

systemctl reload nginx

systemctl restart my-app # If app reads certs directly

<h1>Make executable</h1>

chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-services.sh

---

cert-manager on Kubernetes

cert-manager automates certificate lifecycle management within Kubernetes clusters. It watches Ingress resources, issues certificates via ACME, and stores them as Kubernetes Secrets.

<h1>Install cert-manager with Helm</h1>

helm repo add jetstack https://charts.jetstack.io

helm repo update

helm install cert-manager jetstack/cert-manager \

--namespace cert-manager \

--create-namespace \

--set crds.enabled=true

<h1>ClusterIssuer for Let's Encrypt production</h1>

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-prod

spec:

acme:

server: https://acme-v02.api.letsencrypt.org/directory

email: [email protected]

privateKeySecretRef:

name: letsencrypt-prod-key

solvers:

- http01:

ingress:

class: nginx

<h1>Certificate resource for automatic issuance</h1>

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

name: app-tls

namespace: production

spec:

secretName: app-tls-secret

issuerRef:

name: letsencrypt-prod

kind: ClusterIssuer

dnsNames:

- app.example.com

- api.example.com

renewBefore: 720h # Renew 30 days before expiry

<h1>Ingress with automatic TLS</h1>

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: app-ingress

annotations:

cert-manager.io/cluster-issuer: letsencrypt-prod

spec:

tls:

- hosts:

- app.example.com

secretName: app-tls-secret

rules:

- host: app.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: app-service

port:

number: 80

---

Wildcard Certificates

Wildcard certificates (*.example.com) cover all subdomains at one level. They require DNS-01 validation because the CA cannot verify ownership of all possible subdomains via HTTP.

<h1>cert-manager wildcard with Cloudflare DNS validation</h1>

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-wildcard

spec:

acme:

server: https://acme-v02.api.letsencrypt.org/directory

email: [email protected]

privateKeySecretRef:

name: letsencrypt-wildcard-key

solvers:

- dns01:

cloudflare:

email: [email protected]

apiTokenSecretRef:

name: cloudflare-api-token

key: api-token

---

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

name: wildcard-tls

spec:

secretName: wildcard-tls-secret

issuerRef:

name: letsencrypt-wildcard

kind: ClusterIssuer

dnsNames:

- "*.example.com"

- example.com

Certificate TypeUse CaseValidationIssuance Time

Single domainOne specific hostnameHTTP-0110-30 seconds
Multi-domain (SAN)2-100 specific hostnamesHTTP-0110-30 seconds
WildcardAll subdomains at one levelDNS-01 only30-120 seconds
Wildcard + apex*.example.com + example.comDNS-0130-120 seconds

---

HSTS Configuration

HTTP Strict Transport Security (HSTS) tells browsers to always use HTTPS, preventing protocol downgrade attacks.

<h1>Nginx HSTS header</h1>

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

<h1>Verify HSTS is working</h1>

curl -sI https://example.com | grep -i strict

HSTS deployment should be gradual to avoid locking out users if HTTPS is misconfigured. Start with a short max-age, verify, then increase.

Phasemax-ageDurationAction

Test300 (5 min)1 weekVerify all pages load over HTTPS
Ramp86400 (1 day)2 weeksMonitor for mixed content errors
Production31536000 (1 year)OngoingAll verified, full protection
Preload31536000 + preloadPermanentSubmit to hstspreload.org

---

Certificate Monitoring

<h1>Check certificate expiry date</h1>

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \

| openssl x509 -noout -dates

<h1>Prometheus blackbox exporter for cert monitoring</h1>

<h1>blackbox.yml</h1>

modules:

tls_connect:

prober: tcp

timeout: 5s

tcp:

tls: true

tls_config:

insecure_skip_verify: false

<h1>Simple monitoring script</h1>

#!/bin/bash

DOMAINS="example.com api.example.com app.example.com"

WARN_DAYS=14

for domain in $DOMAINS; do

expiry=$(echo | openssl s_client -connect "$domain:443" \

-servername "$domain" 2>/dev/null \

| openssl x509 -noout -enddate \

| cut -d= -f2)

expiry_epoch=$(date -d "$expiry" +%s)

now_epoch=$(date +%s)

days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

if [ "$days_left" -lt "$WARN_DAYS" ]; then

echo "WARNING: $domain certificate expires in $days_left days"

fi

done

For DNS configuration required for certificate validation, see our DNS management guide. Teams deploying certificates behind a CDN should check our CDN configuration guide for TLS termination best practices. For the complete server hardening checklist including TLS configuration, see our SSH hardening guide.

---

Frequently Asked Questions

Why are Let's Encrypt certificates only valid for 90 days?

Short validity periods encourage automation. A 90-day certificate that auto-renews at 60 days gives you a 30-day window to fix renewal failures before expiry. Longer validity periods (1-2 years) create a false sense of security because manual renewal processes are forgotten or undocumented. Automated renewal with 90-day certificates is more reliable than manual renewal with 1-year certificates.

Can I use Let's Encrypt for production?

Absolutely. Let's Encrypt issues over 400 million certificates and is trusted by all major browsers. The certificates provide the same TLS encryption as paid certificates. The only difference is that Let's Encrypt does not offer Organization Validation (OV) or Extended Validation (EV) certificates, which display the organization name in the browser. For API servers and web applications, Domain Validation (DV) from Let's Encrypt is sufficient.

How do I handle certificate errors in staging environments?

Use the Let's Encrypt staging server (acme-staging-v02.api.letsencrypt.org) for testing. It issues certificates from a non-trusted CA, so browsers will show warnings, but it does not count against rate limits. For local development, use mkcert to create locally trusted certificates without touching Let's Encrypt at all.

What happens when cert-manager fails to renew?

cert-manager retries renewal on an exponential backoff schedule. If renewal fails repeatedly, the Certificate resource status shows the failure reason. Set up Prometheus alerts on cert_manager_certificate_ready_status and cert_manager_certificate_expiration_timestamp_seconds metrics. Alert when a certificate is within 14 days of expiry and not marked as ready.

Should I pin certificates in my application?

No. Certificate pinning (HPKP) was deprecated in 2018 because it creates more outages than it prevents. A single misconfigured pin can make your site completely unreachable with no recovery path. Use Certificate Transparency (CT) logs to monitor for unauthorized certificate issuance instead.