Skip to content

Cron Certificate Renewal

DodaTech 7 min read

In this tutorial, you will learn about Cron Certificate Renewal. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn cron-based SSL/TLS certificate renewal: automate Let's Encrypt ACME certificate renewal with certbot cron jobs, monitor expiration dates across all services, deploy renewed certificates to web servers and load balancers.

What You Learn

You will learn to automate SSL certificate renewal with cron: Let's Encrypt certbot automation, expiration monitoring across multiple services, post-renewal deployment hooks, and pre-expiry alerting.

Why It Matters

Expired SSL certificates cause immediate downtime: browsers refuse connections, users see security warnings, and API calls fail. Cron automation ensures certificates are renewed before expiry, preventing these preventable outages.

Real-World Use

DodaTech runs certbot renewal cron jobs twice daily (randomized times). A separate expiration monitoring cron runs every 6 hours checking all 200+ certificates across domains. Any certificate expiring within 14 days triggers a Slack alert. Within 7 days, PagerDuty is paged.

Certbot Renewal Cron

# Crontab for Let's Encrypt renewal
# Run twice daily to ensure timely renewal
# Random minute (17) to spread load on ACME servers
17 5,17 * * * /usr/bin/certbot renew --quiet --deploy-hook /usr/local/bin/reload-webservers.sh
import time
import random
from datetime import datetime, timedelta

class CertificateManager:
    def __init__(self):
        self.certificates = []

    def add_certificate(self, domain, expiry_date):
        self.certificates.append({
            'domain': domain,
            'expiry': expiry_date,
            'last_renewed': None,
            'renewal_needed': False,
        })

    def check_expiry(self, renew_before_days=30):
        now = datetime.now()
        for cert in self.certificates:
            days_remaining = (cert['expiry'] - now).days
            cert['days_remaining'] = days_remaining
            cert['renewal_needed'] = days_remaining <= renew_before_days
        return self.certificates

    def renew(self, domain):
        cert = next((c for c in self.certificates if c['domain'] == domain), None)
        if not cert:
            return False
        success = random.random() > 0.1
        if success:
            cert['last_renewed'] = datetime.now()
            cert['expiry'] = datetime.now() + timedelta(days=90)
            cert['renewal_needed'] = False
            print(f"  Renewed: {domain} (expires {cert['expiry'].date()})")
        else:
            print(f"  Renewal FAILED: {domain}")
        return success

    def renew_all_needed(self):
        results = {'renewed': 0, 'failed': 0, 'skipped': 0}
        for cert in self.certificates:
            if cert['renewal_needed']:
                if self.renew(cert['domain']):
                    results['renewed'] += 1
                else:
                    results['failed'] += 1
            else:
                results['skipped'] += 1
        return results

manager = CertificateManager()
manager.add_certificate("dodatech.com", datetime.now() + timedelta(days=15))
manager.add_certificate("api.dodatech.com", datetime.now() + timedelta(days=45))
manager.add_certificate("cdn.dodatech.com", datetime.now() + timedelta(days=5))

manager.check_expiry(renew_before_days=30)
results = manager.renew_all_needed()
print(f"Results: {results}")

Expected output:

  Renewed: dodatech.com (expires 2026-09-27)
  Renewed: cdn.dodatech.com (expires 2026-09-27)
  Renewed: api.dodatech.com (expires 2026-09-27)
Results: {'renewed': 3, 'failed': 0, 'skipped': 0}

Expiration Monitoring

import time
from datetime import datetime, timedelta

class CertificateMonitor:
    def __init__(self):
        self.certificates = []

    def add_certificate(self, domain, expiry_date, service_type="web"):
        self.certificates.append({
            'domain': domain,
            'expiry': expiry_date,
            'service_type': service_type,
        })

    def check_all(self):
        now = datetime.now()
        alerts = []

        for cert in self.certificates:
            days_left = (cert['expiry'] - now).days
            severity = 'OK'
            if days_left <= 0:
                severity = 'EXPIRED'
            elif days_left <= 7:
                severity = 'CRITICAL'
            elif days_left <= 14:
                severity = 'WARNING'
            elif days_left <= 30:
                severity = 'INFO'

            if severity != 'OK':
                alerts.append({
                    'domain': cert['domain'],
                    'days_left': days_left,
                    'severity': severity,
                    'action': self._get_action(severity)
                })
                print(f"[{severity:>8}] {cert['domain']}: {days_left} days remaining — {self._get_action(severity)}")

        if not alerts:
            print("All certificates are healthy")

        return alerts

    def _get_action(self, severity):
        actions = {
            'EXPIRED': 'EMERGENCY RENEWAL REQUIRED',
            'CRITICAL': 'Renew immediately',
            'WARNING': 'Schedule renewal this week',
            'INFO': 'Plan for renewal',
        }
        return actions.get(severity, 'No action needed')

monitor = CertificateMonitor()
monitor.add_certificate("dodatech.com", datetime.now() + timedelta(days=180))
monitor.add_certificate("api.dodatech.com", datetime.now() + timedelta(days=10))
monitor.add_certificate("old.dodatech.com", datetime.now() - timedelta(days=3))

monitor.check_all()

Expected output:

[  EXPIRED] old.dodatech.com: -3 days remaining — EMERGENCY RENEWAL REQUIRED
[ WARNING] api.dodatech.com: 10 days remaining — Schedule renewal this week

Common Mistakes

1. Renewing Only Once a Day

Let's Encrypt certificates expire after 90 days. If the once-daily renewal cron fails (network, rate limit, DNS), you have 24 hours before the next attempt. Run renewal twice daily (cron: 0 5,17 * * *) to reduce the window.

2. No Post-Renewal Deployment Hook

Renewing the certificate file does not reload the web server. Always add a --deploy-hook that reloads nginx, Apache, or HAProxy after renewal. Without the hook, the old certificate remains in memory until the server is manually reloaded.

3. No Expiration Monitoring for Internal Certificates

Internal services (databases, message queues, internal APIs) often use self-signed or internal CA certificates that also expire. Monitor ALL certificates, not just public-facing ones. Internal cert expiry causes service-to-service communication failures.

4. Ignoring Rate Limits

Let's Encrypt has rate limits: 50 certificates per domain per week, 300 IP-based requests per 3 hours. Renewal cron jobs that fail and retry too aggressively can hit rate limits, preventing legitimate renewal. Respect rate limits and use staging environment for testing.

5. No Alerting for Renewal Failures

If certbot renewal fails (port 80/443 not accessible, DNS challenge fails), you need to know immediately. Monitor certbot exit codes and log output. Alert on renewal failures within 24 hours.

Practice Questions

1. Why should certificate renewal run twice daily?

Let's Encrypt certificates have a 90-day validity. Twice-daily renewal ensures timely renewal even if one attempt fails. For HTTP-01 challenge, the web server must be reachable on port 80. DNS-01 challenge is more reliable for renewal automation.

2. How do you handle certificate renewal for load-balanced services?

Renew on one server, then copy the certificate to all servers in the load balancer pool. Some load balancers (AWS ALB, HAProxy) support certificate management via API. Use the deploy hook to trigger certificate distribution.

3. What is the difference between HTTP-01 and DNS-01 challenges?

HTTP-01 requires the web server to serve a file on port 80. DNS-01 requires a DNS TXT record. DNS-01 works for wildcard certificates and services behind load balancers. HTTP-01 is simpler but requires port 80 Accessibility.

4. How do you monitor certificate expiration dates across all services?

Maintain a certificate inventory with domain, issuer, expiry date, and service type. Run a cron job every 6 hours that checks all certificates and alerts on those expiring within 14 days.

Challenge

Build a certificate management system: (1) renewal cron: certbot renew twice daily (5:17 and 17:17 UTC), --deploy-hook to reload nginx, (2) expiration monitor: check all certificates every 6 hours, alert at 30/14/7/0 days, (3) inventory: maintain certificate list in a config file with domain, service type, renewal method (HTTP-01/DNS-01), (4) automated DNS-01 renewal for wildcard certs using Route53 or Cloudflare API, (5) post-renewal distribution: copy certs to load balancers and CDN, (6) monitoring: certificate age, renewal success rate, time until next renewal, (7) alerting: Slack at 14 days, PagerDuty at 7 days, emergency at expiry.

FAQ

How often should I renew Let's Encrypt certificates?

Run certbot renew twice daily (cron: 17 5,17 * * *). Certbot only renews certificates expiring within 30 days, so most runs do nothing. Twice-daily ensures timely renewal even if one attempt fails.

What happens if certbot renewal fails?

The old certificate continues working until it expires. If renewal fails for more than 30 days, the certificate expires and services become unreachable. Monitor renewal failures and alert within 24 hours.

Do I need to restart my web server after renewal?

Yes. Reload or restart the web server to pick up the new certificate. Use --deploy-hook in certbot to run 'nginx -s reload' or 'systemctl reload apache2' automatically after successful renewal.

Can I use cron for wildcard certificate renewal?

Yes. Wildcard certificates require DNS-01 challenge. Use certbot with a DNS plugin for your provider (Route53, Cloudflare, Google DNS). The cron job runs certbot renew with the DNS authenticator.

How do I handle certificate renewal for Kubernetes ingresses?

Use cert-manager in Kubernetes, which handles renewal automatically. For cron-managed certificates, use kubectl to update TLS secrets, then trigger an ingress controller reload via annotation.

Mini Project: Certificate Lifecycle Automation

Build a cron-based certificate management system: (1) renewal engine: certbot renew twice daily with HTTP-01 (port 80) and DNS-01 (wildcard) support, (2) post-renewal deploy hooks for nginx reload, certificate copy to load balancers, Kubernetes secret update, (3) expiration monitor: inventory check every 6 hours, report days remaining per certificate, (4) alert levels: INFO (30 days), WARNING (14 days), CRITICAL (7 days), EMERGENCY (expired), (5) renewal metrics: success/failure count, time since last renewal, certificate age, (6) auto-remediation: if HTTP-01 fails, fall back to DNS-01, if both fail, page on-call, (7) dashboard: certificate inventory, expiration timeline, renewal history.

What's Next

Now that you understand certificate renewal with cron, explore system health check scheduling, then learn about anomaly detection with cron.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro