How to Handle SSL Certificate Renewal
Never let your certificates expire unexpectedly
TL;DR
TL;DR (15 minutes):
For Let's Encrypt: Certbot auto-renews via systemd timer or cron - verify with sudo certbot renew --dry-run. For platform-hosted sites (Vercel, Netlify), renewal is automatic. Monitor expiration with free tools like SSL Labs, UptimeRobot, or a simple cron script. Most renewal failures are caused by blocked ports or stopped web servers.
Prerequisites
- An existing SSL certificate installed on your server or platform
- For manual servers: SSH access with sudo privileges
- For Let's Encrypt: Certbot already installed
- Email address for expiration alerts
Certificate Renewal by Platform
| Platform | Renewal Method | Your Action Required |
|---|---|---|
| Vercel | Fully automatic | None - handled by platform |
| Netlify | Fully automatic | None - handled by platform |
| Cloudflare | Fully automatic | None - handled by platform |
| AWS CloudFront + ACM | Automatic (ACM) | None - ACM auto-renews |
| VPS with Certbot | Automatic (timer/cron) | Verify timer is running |
| Commercial certificate | Manual | Renew before expiration |
Check Certificate Expiration Date
From command line
# Check expiration for any domain
echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates
# Output shows:
# notBefore=Jan 1 00:00:00 2024 GMT
# notAfter=Apr 1 00:00:00 2024 GMT
# More detailed certificate info:
echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Using Certbot
# List all certificates and their expiration
sudo certbot certificates
# Output shows:
# Certificate Name: yourdomain.com
# Domains: yourdomain.com www.yourdomain.com
# Expiry Date: 2024-04-01 (VALID: 45 days)
In browser
Click the padlock icon > "Connection is secure" > "Certificate is valid" to view expiration date.
Set Up Automatic Renewal (Certbot)
Verify auto-renewal is configured
# Check if systemd timer exists (most systems)
systemctl list-timers | grep certbot
# Should show something like:
# NEXT LEFT UNIT ACTIVATES
# Mon 2024-01-15 03:42:00 UTC 5h left certbot.timer certbot.service
# Or check cron
cat /etc/cron.d/certbot
# Should show renewal command running twice daily
Test renewal (dry run)
# Always test before relying on auto-renewal
sudo certbot renew --dry-run
# Success output:
# Congratulations, all simulated renewals succeeded:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem (success)
# If this fails, fix the issue before your certificate expires
Set up systemd timer (if missing)
# Create timer file
sudo nano /etc/systemd/system/certbot.timer
# Add content:
[Unit]
Description=Run certbot twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true
[Install]
WantedBy=timers.target
# Create service file
sudo nano /etc/systemd/system/certbot.service
# Add content:
[Unit]
Description=Certbot renewal
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
# Enable and start timer
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
# Verify it's running
systemctl status certbot.timer
Alternative: Cron job
# Add to crontab
sudo crontab -e
# Add this line (runs at 3:30 AM and 3:30 PM)
30 3,15 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
# The --deploy-hook runs only if renewal occurred
# Change nginx to apache2 if using Apache
Set Up Renewal Notifications
Let's Encrypt email notifications
Let's Encrypt emails you 20 days before expiration if not renewed:
# Update email in Certbot
sudo certbot update_account --email your@email.com
# Verify email is set
sudo certbot show_account
Create monitoring script
#!/bin/bash
# save as /usr/local/bin/check-ssl-expiry.sh
DOMAIN="yourdomain.com"
ALERT_DAYS=14
EMAIL="your@email.com"
# Get expiration date
EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
# Convert to epoch
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $ALERT_DAYS ]; then
echo "SSL certificate for $DOMAIN expires in $DAYS_LEFT days!" | \
mail -s "SSL Certificate Expiring: $DOMAIN" $EMAIL
fi
# Make executable and add to cron
chmod +x /usr/local/bin/check-ssl-expiry.sh
sudo crontab -e
# Add: 0 9 * * * /usr/local/bin/check-ssl-expiry.sh
Use external monitoring services
- UptimeRobot: Free SSL monitoring with email/SMS alerts
- SSL Labs: Bookmark your test URL, check periodically
- Datadog/PagerDuty: Enterprise monitoring with SSL checks
- Oh Dear: Affordable monitoring with certificate checks
Security Checklist
Certificate Renewal Security Checklist
- Auto-renewal configured (systemd timer or cron)
- Dry-run test passes successfully
- Deploy hook reloads web server after renewal
- Email notifications configured for Let's Encrypt
- External monitoring set up for expiration alerts
- Port 80 accessible for HTTP-01 challenge (or DNS challenge configured)
- Logs monitored for renewal failures
- Backup renewal process documented
- Multiple domains in certificate are all valid
- Calendar reminder set for annual review
How to Verify It Worked
Check certificate after renewal
# Verify new expiration date
echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates
# Should show dates ~90 days in the future for Let's Encrypt
Check Certbot logs
# View recent renewal attempts
sudo tail -100 /var/log/letsencrypt/letsencrypt.log
# Look for:
# "Cert is due for renewal, auto-renewing..."
# "Congratulations, all renewals succeeded"
Verify web server reloaded
# Check nginx/apache is serving new certificate
# The serial number should change after renewal
echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -serial
Test site functionality
After renewal, verify your site loads correctly over HTTPS with no certificate warnings.
Manual Renewal (When Needed)
Force immediate renewal
# Force renewal even if not due
sudo certbot renew --force-renewal
# Renew specific certificate
sudo certbot certonly --force-renewal -d yourdomain.com -d www.yourdomain.com
# Reload web server after manual renewal
sudo systemctl reload nginx # or apache2
Renew with standalone server
# If web server is causing issues, use standalone mode
sudo systemctl stop nginx
sudo certbot renew --standalone
sudo systemctl start nginx
Common Errors and Troubleshooting
Challenge failed: Connection refused on port 80
# Firewall blocking port 80
sudo ufw allow 80/tcp
sudo ufw status
# Or check iptables
sudo iptables -L -n | grep 80
# Some hosts require port 80 open even for HTTPS sites
# Let's Encrypt needs it for HTTP-01 challenge
Could not bind to port 80
# Another service is using port 80
sudo lsof -i :80
# If it's your web server, use webroot or nginx plugin instead of standalone
sudo certbot renew --webroot -w /var/www/html
# Or use the nginx/apache plugin
sudo certbot renew --nginx
DNS problem: NXDOMAIN for domain
# Domain DNS not resolving
dig yourdomain.com A
# Verify DNS points to your server's IP
# If DNS changed recently, wait for propagation
# For DNS-01 challenge (wildcard certs), ensure TXT records are correct
Too many certificates already issued
# Let's Encrypt rate limit: 50 certs per domain per week
# Wait a week, or use staging for testing:
sudo certbot certonly --staging -d yourdomain.com
# Use --expand to add domains to existing certificate instead of new cert
sudo certbot certonly --expand -d yourdomain.com -d newsubdomain.yourdomain.com
Web server not reloading after renewal
# Add deploy hook to reload automatically
sudo certbot renew --deploy-hook "systemctl reload nginx"
# Or edit /etc/letsencrypt/renewal/yourdomain.com.conf:
# Under [renewalparams], add:
# renew_hook = systemctl reload nginx
# Verify hook is set:
cat /etc/letsencrypt/renewal/yourdomain.com.conf | grep hook
Certificate renewed but site shows old certificate
# Web server needs to reload
sudo systemctl reload nginx # or apache2
# Check nginx is using correct certificate path
grep ssl_certificate /etc/nginx/sites-enabled/*
# Should point to /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# If using a reverse proxy or CDN, their cache may need clearing
Renewal works manually but not automatically
# Check systemd timer is running
systemctl status certbot.timer
# Check for path issues in cron
# Use full paths in cron jobs:
/usr/bin/certbot renew --quiet
# Check cron logs
grep certbot /var/log/syslog
# Ensure certbot user has necessary permissions
Emergency: Certificate Already Expired
If your certificate has already expired, users see security warnings. Act immediately:
# Try immediate renewal
sudo certbot renew --force-renewal
# If that fails, get new certificate
sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com
# Reload web server
sudo systemctl reload nginx
How often do SSL certificates need to be renewed?
Let's Encrypt certificates expire every 90 days, but Certbot attempts renewal when 30 days remain. Commercial certificates typically last 1-2 years. Platform-managed certificates (Vercel, Netlify, Cloudflare) renew automatically with no action required.
What happens if my SSL certificate expires?
Visitors will see a security warning like "Your connection is not private" or "NET::ERR_CERT_DATE_INVALID". Most browsers will block access unless users click through warnings. This causes immediate loss of traffic and severely damages user trust.
Why did my automatic renewal fail?
Common causes include: port 80 blocked by firewall (needed for HTTP-01 challenge), web server stopped or misconfigured, DNS changes that broke domain verification, insufficient disk space, or Certbot/system not running. Check /var/log/letsencrypt/letsencrypt.log for specific errors.
Can I renew my certificate early?
Yes, use sudo certbot renew --force-renewal to renew immediately regardless of expiration date. However, Let's Encrypt has rate limits (50 certificates per domain per week), so don't force-renew unnecessarily.
Do I need to renew platform-managed certificates?
No. Vercel, Netlify, Cloudflare Pages, and similar platforms handle certificate renewal automatically. You don't need to take any action - just ensure your domain remains connected to the platform.
How do I switch from manual to automatic renewal?
If you originally set up certificates manually, you can still enable auto-renewal. Ensure Certbot's systemd timer or cron job is configured, then run sudo certbot renew --dry-run to verify it works. The renewal configuration is stored in /etc/letsencrypt/renewal/.
Run a free security scan to check certificate validity and get expiration alerts.
Start Free Scan