Web Security

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.

December 1, 20257 min readShipSafer Team

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.

TLS
SSL
certificates
Let's Encrypt
Certbot
cert-manager
Kubernetes
ACM
web security

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.