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 Type | Use Case | Validation | Issuance Time |
| Single domain | One specific hostname | HTTP-01 | 10-30 seconds |
| Multi-domain (SAN) | 2-100 specific hostnames | HTTP-01 | 10-30 seconds |
| Wildcard | All subdomains at one level | DNS-01 only | 30-120 seconds |
| Wildcard + apex | *.example.com + example.com | DNS-01 | 30-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.
| Phase | max-age | Duration | Action |
| Test | 300 (5 min) | 1 week | Verify all pages load over HTTPS |
| Ramp | 86400 (1 day) | 2 weeks | Monitor for mixed content errors |
| Production | 31536000 (1 year) | Ongoing | All verified, full protection |
| Preload | 31536000 + preload | Permanent | Submit 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.