TLS Certificate Management: Let's Encrypt, Auto-Renewal, and Multi-Domain Certs
A comprehensive guide to TLS certificate types (DV, OV, EV, wildcard), obtaining and auto-renewing certificates with Certbot and cert-manager for Kubernetes, using AWS ACM, and monitoring for certificate expiry.
Certificate management is one of those infrastructure topics that is easy to ignore until a certificate expires and your production site goes down with a browser-blocking "Your connection is not private" warning. Major outages — at Stripe, Microsoft, LinkedIn, and hundreds of other companies — have been caused by expired certificates. This guide covers certificate types, acquisition strategies, automation, and monitoring to ensure expiry is never a surprise.
Certificate Types: DV, OV, EV, and Wildcard
Domain Validated (DV) Certificates
DV certificates verify that the certificate requester controls the domain. The validation process is automated (via HTTP or DNS challenges) and takes seconds to minutes. Let's Encrypt issues DV certificates. They provide full encryption and are indistinguishable from OV/EV certificates in terms of the cryptographic protection they provide. The lock icon in the browser appears the same.
DV certs are appropriate for virtually all web applications and APIs.
Organization Validated (OV) Certificates
OV certificates require the CA to verify the organization's legal existence and physical address. The validation process takes 1-3 business days and requires documents. OV certs display the organization name in the certificate details (visible to users who click the lock icon). They are more expensive ($50-$500/year) and provide no meaningful additional security over DV.
OV certs are sometimes required by enterprise procurement policies or for certain regulated industries.
Extended Validation (EV) Certificates
EV certificates require the most rigorous validation and historically displayed the organization name prominently in the browser address bar (the "green bar"). However, Chrome and Firefox removed the green bar display in 2019, concluding that users did not understand its significance. EV certificates now display identically to DV certificates in modern browsers. They are expensive and provide no user-visible or cryptographic advantage.
EV certificates are not recommended for new deployments. Legacy systems that have them can continue to use them, but there is no reason to seek EV for new infrastructure.
Wildcard Certificates
A wildcard certificate covers an entire subdomain level. *.example.com covers app.example.com, api.example.com, www.example.com, but not example.com itself (add it as a SAN) and not sub.app.example.com (a second-level wildcard is not covered).
Wildcard certificates are issued via DNS challenge (not HTTP challenge) because the CA needs to verify you control the domain at the DNS level, not just a specific subdomain. Let's Encrypt issues wildcard certificates via the --manual --preferred-challenges dns or via DNS provider plugins.
Multi-Domain (SAN) Certificates
Subject Alternative Name (SAN) certificates cover multiple domains in a single certificate. Let's Encrypt supports up to 100 SANs per certificate. This is useful when you have related domains (example.com, www.example.com, example.io, app.example.com) that you want to manage with a single cert.
Let's Encrypt and Certbot
Let's Encrypt is the dominant free CA, issuing over 3 million certificates per day. The standard client is Certbot.
Obtaining certificates for common scenarios:
Single domain:
sudo certbot --nginx -d example.com -d www.example.com
Wildcard certificate (requires DNS plugin for your DNS provider):
# Install the Cloudflare DNS plugin
sudo apt install python3-certbot-dns-cloudflare
# Create Cloudflare API credentials file
cat > /etc/letsencrypt/cloudflare.ini << EOF
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
# Obtain wildcard certificate
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.example.com" \
-d "example.com"
DNS plugins exist for Route53, GCP Cloud DNS, DigitalOcean, Namecheap, and most major providers.
Certificate storage structure:
/etc/letsencrypt/live/example.com/
├── cert.pem # Certificate only (use fullchain.pem instead)
├── chain.pem # Intermediate certificate
├── fullchain.pem # cert.pem + chain.pem (use this in your web server config)
└── privkey.pem # Private key
Renewal configuration: Certbot stores renewal configurations in /etc/letsencrypt/renewal/example.com.conf. This file contains all parameters needed to renew the certificate without manual intervention.
The renewal systemd service:
# Verify the timer is active
systemctl list-timers | grep certbot
# Check renewal is configured correctly
sudo certbot renew --dry-run
# View renewal configuration
cat /etc/letsencrypt/renewal/example.com.conf
Post-renewal hooks: After a certificate is renewed, your web server needs to reload to pick up the new certificate. Configure a deploy hook:
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Certbot calls deploy hooks automatically after successful renewal.
cert-manager for Kubernetes
If you deploy on Kubernetes, cert-manager is the standard solution for automated certificate management. It integrates with Let's Encrypt (and other CAs) and stores certificates as Kubernetes Secrets, automatically renewing them before expiry.
Installing cert-manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
Creating a ClusterIssuer for Let's Encrypt production:
# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
# For wildcard certs, add a DNS01 solver:
# - dns01:
# cloudflare:
# apiTokenSecretRef:
# name: cloudflare-api-token
# key: api-token
Requesting a certificate via Ingress annotation (automatic provisioning):
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls-cert # cert-manager will create/update this Secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
cert-manager watches for Ingress resources with the annotation, provisions a certificate from Let's Encrypt, stores it in the named Secret, and monitors it for expiry. Renewal happens automatically when the certificate has less than 30 days remaining.
Requesting a certificate explicitly (Certificate resource):
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-cert
namespace: default
spec:
secretName: example-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
- api.example.com
duration: 2160h # 90 days
renewBefore: 720h # Renew 30 days before expiry
Checking certificate status:
kubectl get certificates -A
kubectl describe certificate example-cert -n default
kubectl get secret example-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates
AWS Certificate Manager (ACM)
ACM provides free TLS certificates for use with AWS services (CloudFront, ALB, API Gateway, Elastic Beanstalk). ACM certificates are automatically renewed — you do not need to manage renewal at all. This is the biggest advantage of ACM for AWS-native deployments.
Requesting an ACM certificate:
aws acm request-certificate \
--domain-name example.com \
--validation-method DNS \
--subject-alternative-names "www.example.com" "api.example.com" \
--region us-east-1 # CloudFront certificates must be in us-east-1
After requesting, ACM provides DNS CNAME records that you must add to your DNS zone to prove domain ownership. Once the CNAMEs are added and validated, the certificate status becomes ISSUED.
ACM limitations: ACM certificates cannot be exported or installed on EC2 instances directly. They are only usable within AWS services. For EC2-based web servers, you need Let's Encrypt or a commercial CA.
Terraform provisioning:
resource "aws_acm_certificate" "main" {
domain_name = "example.com"
subject_alternative_names = ["www.example.com", "api.example.com"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_acm_certificate_validation" "main" {
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [for record in aws_acm_certificate.main.domain_validation_options : record.resource_record_name]
}
Certificate Expiry Monitoring
Even with automation, certificates can expire due to misconfiguration, DNS issues that block renewal, or infrastructure changes that bypass the renewal process. Monitor certificate expiry as a critical infrastructure metric.
Monitoring with openssl:
# Check days until expiry
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate \
| sed 's/notAfter=//'
Prometheus + blackbox_exporter:
# blackbox.yml
modules:
https_2xx:
prober: http
timeout: 5s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
valid_status_codes: []
method: GET
tls_config:
insecure_skip_verify: false
The blackbox exporter exposes probe_ssl_earliest_cert_expiry — a Unix timestamp of the earliest certificate expiry in the chain. Create an alert:
- alert: SSLCertificateExpirySoon
expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 30
for: 1h
labels:
severity: warning
annotations:
summary: "SSL certificate expiring in {{ $value | humanizeDuration }}"
description: "Certificate for {{ $labels.instance }} expires in {{ $value }} days"
- alert: SSLCertificateExpiryCritical
expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 7
for: 1h
labels:
severity: critical
External monitoring services: If you do not run Prometheus, services like UptimeRobot, Pingdom, and Better Uptime all offer TLS expiry monitoring with email/Slack alerts. This is worthwhile as a second layer even if you have internal monitoring — external monitors are not affected by your infrastructure being down.
The combination of automated renewal (Certbot with deploy hooks, cert-manager, or ACM) plus expiry monitoring means certificate expiry becomes a non-event. The monitor exists to catch the edge cases where automation fails — and those failures do happen.