
Certificate rotation goes wrong in predictable ways: the new certificate is installed but the old one is still being served, the certificate chain is incomplete, or the rotation happens on some servers but not all. The result is either downtime or a security warning that's just as damaging.
Here's a practical guide to rotating SSL certificates correctly, whether you're doing it manually or automating it with Let's Encrypt.
# Check the certificate being served (not just what's on disk)
echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -dates -subject
# Output:
# subject=CN = yourdomain.com
# notBefore=Jan 1 00:00:00 2026 GMT
# notAfter=Apr 1 00:00:00 2026 GMT
The key here is that you're checking what the server is actually serving, not what's on disk. These can differ if nginx/Apache hasn't been reloaded.
An incomplete chain causes browser warnings even with a valid leaf certificate:
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -text | grep -A5 "Issuer:"
Your certificate chain should include: your domain certificate → intermediate certificate(s) → root certificate. Most certificate files from commercial CAs (and Let's Encrypt) include the intermediates automatically.
# Back up existing certificates before making changes
cp /etc/nginx/ssl/yourdomain.crt /etc/nginx/ssl/yourdomain.crt.bak
cp /etc/nginx/ssl/yourdomain.key /etc/nginx/ssl/yourdomain.key.bak
# Copy new certificate files
cp new_certificate.crt /etc/nginx/ssl/yourdomain.crt
cp new_private.key /etc/nginx/ssl/yourdomain.key
Make sure the certificate file includes the full chain (leaf cert + intermediate certs concatenated):
# Concatenate leaf cert and intermediates if separate files
cat yourdomain_cert.crt intermediate_bundle.crt > /etc/nginx/ssl/yourdomain.crt
nginx -t
# Output: nginx: configuration file /etc/nginx/nginx.conf test is successful
Never reload without testing. A configuration error here will take your site down on reload.
# Reload gracefully — active connections complete, new connections use new config
nginx -s reload
# Or:
systemctl reload nginx
Use reload, not restart. Restart drops active connections; reload completes them gracefully.
echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -dates
Confirm the notAfter date matches your new certificate's expiry.
# Back up existing certs
cp /etc/apache2/ssl/yourdomain.crt /etc/apache2/ssl/yourdomain.crt.bak
cp /etc/apache2/ssl/yourdomain.key /etc/apache2/ssl/yourdomain.key.bak
# Place new certificate
cp new_certificate.crt /etc/apache2/ssl/yourdomain.crt
cp new_private.key /etc/apache2/ssl/yourdomain.key
# Test configuration
apachectl configtest
# Reload gracefully
systemctl reload apache2
Your Apache SSL configuration should point to the certificate and chain:
SSLCertificateFile /etc/apache2/ssl/yourdomain.crt
SSLCertificateKeyFile /etc/apache2/ssl/yourdomain.key
# For Apache < 2.4.8, use SSLCertificateChainFile for intermediate certs
# For Apache >= 2.4.8, include intermediates in SSLCertificateFile
Load balancers add complexity because certificates may be terminated at the load balancer, at the origin servers, or both.
Most managed load balancers (AWS ALB, GCP Load Balancing, Cloudflare) handle certificates directly. Rotation is done through their dashboards and takes effect without touching your origin servers.
AWS ALB:
# Via AWS CLI — update the certificate on your listener
aws acm import-certificate \
--certificate fileb://new_cert.pem \
--private-key fileb://new_key.pem \
--certificate-chain fileb://chain.pem
# Or use ACM Certificate Manager for auto-renewed certs
Check each origin server is serving the correct certificate if you terminate SSL at the origins too:
for server in 10.0.1.10 10.0.1.11 10.0.1.12; do
echo -n "$server: "
echo | openssl s_client -connect $server:443 2>/dev/null \
| openssl x509 -noout -enddate
done
For zero-downtime rotation across multiple origin servers:
# After updating each server
echo | openssl s_client -connect $server:443 2>/dev/null \
| openssl x509 -noout -enddate
# Verify no connection errors
curl -I --resolve yourdomain.com:443:$server https://yourdomain.com | head -5
Let's Encrypt certificates expire every 90 days. Certbot handles renewal automatically, but the renewal process can fail silently if not monitored.
# Certbot installs a systemd timer or cron job automatically
# Verify it's running:
systemctl status certbot.timer
# Test the renewal process without actually renewing
certbot renew --dry-run
# Force renewal to test the full process
certbot renew --force-renewal
Certbot renews the certificate files but doesn't automatically reload nginx. Add a post-renewal hook:
# Create /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Without this hook, Certbot renews the files but nginx continues serving the old certificate from memory until manually reloaded.
# Check when your certificate was last renewed
certbot certificates
# Check systemd timer next scheduled run
systemctl list-timers certbot.timer
The certbot timer runs twice daily. If renewal fails, it retries on subsequent runs — but only for a limited period before the certificate expires.
| Problem | Symptom | Fix |
|---|---|---|
| Nginx not reloaded | Browser still sees old cert | systemctl reload nginx |
| Incomplete chain | Browser warning about cert chain | Concatenate intermediates into cert file |
| Wrong domain in cert | Browser shows cert name mismatch | Ensure SAN includes all domains |
| Post-renewal hook missing | Certbot renews but nginx uses old cert | Add deploy hook to reload nginx/apache |
| Load balancer using old cert | LB terminates TLS with old cert | Update cert at the load balancer |
| Multiple servers, partial update | Inconsistent cert between servers | Roll out to all servers, verify each |
Manual rotation processes fail when someone forgets. Automated rotation processes fail when a hook breaks or a renewal silently errors. Either way, you want an alert before the certificate expires.
Domain Monitor monitors your SSL certificates and alerts you when a certificate is approaching expiry — typically 30 days and 7 days before expiry. This catches both manual renewal failures and broken automated renewal processes before users see a security warning. Create a free account.
See Let's Encrypt renewal failed: common causes and fixes for troubleshooting specifically Let's Encrypt renewal issues, and complete guide to SSL certificates for the broader context on certificate management.
When your site goes down, your status page becomes the most important page you have. Here's why it matters, what happens when you don't have one, and what a good status page does during a real outage.
Read moreYour domain is resolving, but pointing to the wrong server — showing old content, a previous host's page, or someone else's site entirely. Here's what causes this and how to diagnose it.
Read moreUptime monitoring isn't foolproof. Single-location monitors, wrong health check endpoints, long check intervals, and false positives can all cause real downtime to go undetected. Here's what to watch out for.
Read moreLooking to monitor your website and domains? Join our platform and start today.