SSL Certificate Management Overview
Implement automated SSL/TLS certificate management across infrastructure, including provisioning, renewal, monitoring, and secure distribution to services.
When to Use HTTPS/TLS enablement Certificate renewal automation Multi-domain certificate management Wildcard certificate handling Certificate monitoring and alerts Zero-downtime certificate rotation Internal PKI management Implementation Examples 1. Let's Encrypt with Cert-Manager
cert-manager-setup.yaml
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: admin@myapp.com privateKeySecretRef: name: letsencrypt-prod solvers: # HTTP-01 solver for standard domains - http01: ingress: class: nginx selector: dnsNames: - "myapp.com" - "www.myapp.com"
# DNS-01 solver for wildcard domains
- dns01:
route53:
region: us-east-1
hostedZoneID: Z1234567890ABC
accessKeyID: AKIAIOSFODNN7EXAMPLE
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-key
selector:
dnsNames:
- "*.myapp.com"
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: myapp-tls namespace: production spec: secretName: myapp-tls-secret issuerRef: name: letsencrypt-prod kind: ClusterIssuer commonName: myapp.com dnsNames: - myapp.com - www.myapp.com - api.myapp.com - "*.myapp.com" duration: 2160h # 90 days renewBefore: 720h # 30 days before expiry
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp namespace: production annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: nginx tls: - hosts: - myapp.com - www.myapp.com secretName: myapp-tls-secret rules: - host: myapp.com http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80
- AWS ACM Certificate Management
acm-certificates.yaml
resource "aws_acm_certificate" "main" { domain_name = "myapp.com" validation_method = "DNS"
subject_alternative_names = [ "www.myapp.com", "api.myapp.com", "*.myapp.com" ]
tags = { Name = "myapp-certificate" }
lifecycle { create_before_destroy = true } }
Create Route53 validation records
resource "aws_route53_record" "cert_validation" { for_each = { for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } }
allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = aws_route53_zone.main.zone_id }
Validate certificate
resource "aws_acm_certificate_validation" "main" { certificate_arn = aws_acm_certificate.main.arn timeouts { create = "5m" }
depends_on = [aws_route53_record.cert_validation] }
Use in ALB
resource "aws_lb_listener" "https" { load_balancer_arn = aws_lb.main.arn port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01" certificate_arn = aws_acm_certificate_validation.main.certificate_arn
default_action { type = "forward" target_group_arn = aws_lb_target_group.main.arn } }
- Certificate Monitoring and Renewal
!/bin/bash
certificate-monitor.sh - Monitor and alert on certificate expiration
set -euo pipefail
ALERT_DAYS=30 ALERT_EMAIL="admin@myapp.com"
Check certificate expiration in Kubernetes
check_k8s_certificates() { echo "Checking Kubernetes certificate expiration..."
kubectl get secrets -A -o json | jq -r '.items[] | select(.type=="kubernetes.io/tls") | "\(.metadata.name) \(.metadata.namespace)"' | \
while read secret namespace; do
cert=$(kubectl get secret "$secret" -n "$namespace" -o jsonpath='{.data.tls\.crt}' | base64 -d)
expiry=$(echo "$cert" | openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_until=$((($expiry_epoch - $now_epoch) / 86400))
if [ $days_until -lt $ALERT_DAYS ]; then
echo "WARNING: Certificate $secret in namespace $namespace expires in $days_until days"
echo "Certificate $secret expires on $expiry" | \
mail -s "Certificate Expiration Alert" "$ALERT_EMAIL"
fi
done
}
Check AWS ACM certificates
check_acm_certificates() { echo "Checking AWS ACM certificate expiration..."
aws acm list-certificates \
--query 'CertificateSummaryList[*].CertificateArn' \
--output text | tr '\t' '\n' | \
while read arn; do
expiry=$(aws acm describe-certificate --certificate-arn "$arn" \
--query 'Certificate.NotAfter' --output text)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_until=$((($expiry_epoch - $now_epoch) / 86400))
if [ $days_until -lt $ALERT_DAYS ]; then
domain=$(aws acm describe-certificate --certificate-arn "$arn" \
--query 'Certificate.DomainName' --output text)
echo "WARNING: Certificate for $domain expires in $days_until days"
echo "ACM Certificate $domain expires on $expiry" | \
mail -s "Certificate Expiration Alert" "$ALERT_EMAIL"
fi
done
}
Main execution
check_k8s_certificates check_acm_certificates
echo "Certificate check complete"
- Automated Certificate Renewal
certificate-renewal-cronjob.yaml
apiVersion: batch/v1 kind: CronJob metadata: name: certificate-renewal namespace: operations spec: schedule: "0 2 * * 0" # Weekly at 2 AM Sunday jobTemplate: spec: template: spec: serviceAccountName: cert-renewal-sa containers: - name: renewer image: alpine:latest command: - sh - -c - | apk add --no-cache kubectl curl jq openssl
echo "Checking certificate renewal status..."
# Get all certificates
kubectl get certificates -A -o json | jq -r '.items[] | "\(.metadata.name) \(.metadata.namespace)"' | \
while read cert namespace; do
status=$(kubectl get certificate "$cert" -n "$namespace" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
if [ "$status" != "True" ]; then
echo "Renewing certificate: $cert in namespace $namespace"
kubectl annotate certificate "$cert" -n "$namespace" \
cert-manager.io/issue-temporary-certificate="true" \
--overwrite || true
fi
done
echo "Certificate renewal check complete"
restartPolicy: OnFailure
apiVersion: v1 kind: ServiceAccount metadata: name: cert-renewal-sa namespace: operations
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cert-renewal rules: - apiGroups: ["cert-manager.io"] resources: ["certificates"] verbs: ["get", "list", "patch", "annotate"]
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cert-renewal roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cert-renewal subjects: - kind: ServiceAccount name: cert-renewal-sa namespace: operations
- Certificate Pinning
nginx-certificate-pinning.conf
server { listen 443 ssl http2; server_name api.myapp.com;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
# Certificate pinning for API clients
add_header Public-Key-Pins 'pin-sha256="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; pin-sha256="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="; max-age=2592000; includeSubDomains' always;
location / {
proxy_pass http://backend;
}
}
Best Practices ✅ DO Automate certificate renewal Use Let's Encrypt for public certs Monitor certificate expiration Use wildcard certs strategically Implement certificate pinning Rotate certificates regularly Store keys securely Use strong key sizes (2048+ RSA, 256+ ECDSA) ❌ DON'T Manual certificate management Self-signed certs in production Share private keys Ignore expiration warnings Use weak key sizes Mix dev and prod certs Commit certs to git Disable certificate validation Certificate Types Self-signed: Development only Domain Validated (DV): Single domain Wildcard: All subdomains Multi-SAN: Multiple domains Extended Validation (EV): High trust Common Commands
Generate CSR
openssl req -new -key server.key -out server.csr
Check certificate
openssl x509 -in cert.pem -text -noout
Get certificate fingerprint
openssl x509 -in cert.pem -noout -fingerprint -sha256
Renew certificate
certbot renew --force-renewal
Resources Let's Encrypt Documentation Cert-Manager Documentation AWS ACM Documentation OWASP Certificate Pinning