← Back to blog
pingcheck

SSL Certificate Renewal Guide 2026

Complete guide to SSL certificate renewal with Let's Encrypt, certbot auto-renewal, cron jobs, and monitoring alerts. Prevent expired cert outages.

ssltlscertbotlets-encryptdevopssecurity

SSL Certificate Renewal



Your SSL certificate expires in 12 days. You know this because your monitoring just fired an alert — or worse, because a customer reported that your site shows a security warning. Either way, the clock is ticking. Certificate renewal should be automated, tested, and monitored so this situation never occurs. This guide covers the complete renewal lifecycle: manual renewal for the immediate fix, certbot automation for the long-term solution, cron scheduling for reliability, and PingCheck alerts as the safety net. Every command here is tested against real infrastructure running in 2026.

Understanding SSL Certificate Lifespans



Certificate validity periods have shortened steadily:

  • Before 2020: Certificates lasted up to 2 years.
  • 2020-2025: Maximum 397 days (13 months) for paid certificates.
  • Let's Encrypt: 90 days since inception. This is by design — short lifespans reduce the impact of key compromise.
  • 2026 and beyond: Apple and Google are pushing for 90-day maximums across all CAs.


  • The 90-day cycle means auto-renewal is not a convenience — it is a requirement. Manually renewing every 90 days across multiple domains is unsustainable.

    Emergency Manual Renewal with Certbot



    If your certificate is about to expire or already has, renew it manually first. Fix the automation later.

    # Check which certificates certbot manages
    sudo certbot certificates


    Output:

    Certificate Name: luxkern.com
        Serial Number: 04a3b2c1d4e5f6...
        Key Type: ECDSA
        Domains: luxkern.com www.luxkern.com
        Expiry Date: 2026-04-19 23:59:59+00:00 (INVALID: EXPIRED)
        Certificate Path: /etc/letsencrypt/live/luxkern.com/fullchain.pem
        Private Key Path: /etc/letsencrypt/live/luxkern.com/privkey.pem


    Renew a specific certificate



    sudo certbot renew --cert-name luxkern.com --force-renewal


    --force-renewal ignores the "not due for renewal" check. Use this only for emergencies — excessive force-renewals can hit Let's Encrypt rate limits.

    Renew all certificates



    sudo certbot renew


    Certbot checks each managed certificate and renews any that are within 30 days of expiry.

    Restart the web server after renewal



    Certbot renews the certificate files on disk, but your web server is still serving the old certificate from memory. Restart it.

    # Nginx
    sudo systemctl reload nginx

    Apache

    sudo systemctl reload apache2

    Node.js (if using native TLS)

    You must restart the process — Node.js caches certificates at startup

    pm2 restart your-app


    Setting Up Certbot from Scratch



    If you are starting fresh or migrating to Let's Encrypt, here is the complete setup.

    Install certbot



    # Ubuntu/Debian
    sudo apt update
    sudo apt install certbot

    With Nginx plugin

    sudo apt install python3-certbot-nginx

    With Apache plugin

    sudo apt install python3-certbot-apache

    Using snap (recommended by certbot team)

    sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot


    Obtain your first certificate



    # For Nginx (auto-configures the server block)
    sudo certbot --nginx -d luxkern.com -d www.luxkern.com

    For Apache (auto-configures the virtual host)

    sudo certbot --apache -d luxkern.com -d www.luxkern.com

    Standalone mode (when no web server is running)

    sudo certbot certonly --standalone -d luxkern.com -d www.luxkern.com

    DNS challenge (for wildcard certificates)

    sudo certbot certonly --manual --preferred-challenges dns \ -d luxkern.com -d "*.luxkern.com"


    The Nginx and Apache plugins modify your server configuration to reference the new certificate files. The standalone mode starts a temporary web server on port 80 for the HTTP-01 challenge. The DNS challenge requires you to create a TXT record — necessary for wildcard certs.

    Verify the installation



    echo | openssl s_client -servername luxkern.com -connect luxkern.com:443 2>/dev/null \
      | openssl x509 -noout -dates -subject -issuer


    Output:

    notBefore=Apr 7 00:00:00 2026 GMT
    notAfter=Jul 6 23:59:59 2026 GMT
    subject=CN = luxkern.com
    issuer=C = US, O = Let's Encrypt, CN = R11


    Automating Renewal with Cron



    Certbot installs a systemd timer or cron job automatically on most systems. Verify it exists:

    # Check for systemd timer
    systemctl list-timers | grep certbot

    Or check crontab

    sudo crontab -l | grep certbot


    If neither exists, create one manually:

    # Edit root's crontab
    sudo crontab -e


    Add this line:

    0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1


    Breaking this down:

  • 0 3 * * * — runs daily at 3:00 AM. Certbot internally skips certificates that are not due.
  • --quiet — suppresses output unless there is an error.
  • --deploy-hook — runs only when a certificate is actually renewed. This reloads Nginx to pick up the new certificate.
  • >> /var/log/certbot-renew.log — logs output for debugging.


  • Testing the renewal process



    Never wait until expiry to find out your renewal is broken. Dry-run it:

    sudo certbot renew --dry-run


    Output:

    Simulating renewal of an existing certificate for luxkern.com and www.luxkern.com
    
  • - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  • Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/luxkern.com/fullchain.pem (success)


    If the dry run fails, you have a configuration issue to fix before the real renewal attempt. Common failures:

  • Port 80 blocked — Let's Encrypt needs to reach port 80 for the HTTP-01 challenge.
  • DNS not pointing to this server — the challenge URL resolves to a different IP.
  • Certbot version too old — Let's Encrypt occasionally deprecates old ACME protocol versions.


  • Renewal with Docker and Reverse Proxies



    Docker Compose with Nginx and Certbot



    # docker-compose.yml
    version: '3.8'

    services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - certbot-webroot:/var/www/certbot:ro - certbot-certs:/etc/letsencrypt:ro depends_on: - app

    certbot: image: certbot/certbot volumes: - certbot-webroot:/var/www/certbot - certbot-certs:/etc/letsencrypt entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait $${!}; done'"

    app: build: . expose: - "3000"

    volumes: certbot-webroot: certbot-certs:


    The certbot container runs in a loop, checking for renewal every 12 hours. The shared volumes ensure Nginx always has access to the latest certificates.

    Initial certificate in Docker



    # First time: obtain the certificate
    docker compose run --rm certbot certonly \
      --webroot -w /var/www/certbot \
      -d luxkern.com -d www.luxkern.com \
      --email ops@luxkern.com --agree-tos --no-eff-email

    Reload Nginx to pick up the new certificate

    docker compose exec nginx nginx -s reload


    Renewal for Wildcard Certificates



    Wildcard certificates (*.luxkern.com) require DNS-01 challenges. Automate this with a DNS plugin.

    Cloudflare DNS plugin



    # Install the plugin
    sudo apt install python3-certbot-dns-cloudflare

    Create credentials file

    sudo mkdir -p /etc/letsencrypt cat <<EOF | sudo tee /etc/letsencrypt/cloudflare.ini dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN EOF sudo chmod 600 /etc/letsencrypt/cloudflare.ini

    Obtain wildcard certificate

    sudo certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \ -d luxkern.com \ -d "*.luxkern.com"


    The DNS plugin creates and cleans up the TXT record automatically. Renewal works with the same cron job — certbot remembers which plugin was used.

    Route 53 DNS plugin (AWS)



    sudo apt install python3-certbot-dns-route53

    Uses AWS credentials from environment or ~/.aws/credentials

    sudo certbot certonly \ --dns-route53 \ -d luxkern.com \ -d "*.luxkern.com"


    Post-Renewal Hooks



    Certbot supports three types of hooks:

    sudo certbot renew \
      --pre-hook "echo 'Starting renewal'" \
      --deploy-hook "systemctl reload nginx && curl -s -X POST https://hooks.slack.com/services/T.../B.../xxx -d '{\"text\": \"SSL renewed for luxkern.com\"}'" \
      --post-hook "echo 'Renewal attempt complete'"


  • --pre-hook runs before any renewal attempt.
  • --deploy-hook runs only when a certificate is actually renewed.
  • --post-hook runs after all renewal attempts, regardless of outcome.


  • The deploy hook above reloads Nginx and sends a Slack notification. You can also use it to restart Docker containers, purge CDN caches, or update load balancer listeners.

    Monitoring Renewal with PingCheck



    Automation fails silently. Your cron job might stop running because the server ran out of disk space. The DNS plugin might lose access because someone rotated the API token. The web server might not reload properly.

    Luxkern PingCheck monitors your SSL certificates independently of your renewal process:

  • Add your domains to PingCheck.
  • Set SSL monitoring to check certificate expiry.
  • Configure thresholds — alert at 30 days, escalate at 7 days.
  • Set alert channels — email, Slack, PagerDuty, or webhook.


  • PingCheck acts as an independent safety net. Even if certbot, your cron job, and your deploy hook all fail, PingCheck catches the approaching expiry and alerts you with enough time to intervene manually.

    Try PingCheck free — no credit card required.

    Monitoring Cron Job Execution



    The renewal cron itself needs monitoring. If it stops running, you will not know until the certificate expires.

    # Add a heartbeat ping after successful renewal check
    0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx" && curl -s https://hc-ping.luxkern.com/your-check-id > /dev/null


    The heartbeat ping tells your monitoring system that the cron job ran. If the ping stops arriving, you get an alert. This is the pattern described in how to monitor cron jobs.

    Handling Rate Limits



    Let's Encrypt enforces rate limits to prevent abuse:

  • 50 certificates per registered domain per week.
  • 5 duplicate certificates per week (same set of domain names).
  • 300 new orders per account per 3 hours.
  • Failed validations: 5 failures per hostname per hour.


  • If you hit a rate limit:

    # Check your current certificates to avoid unnecessary renewals
    sudo certbot certificates

    Use the staging environment for testing

    sudo certbot certonly --staging --nginx -d test.luxkern.com


    The staging environment has much higher rate limits and issues certificates that browsers do not trust — perfect for testing your automation.

    Migrating from Paid Certificates to Let's Encrypt



    If you are currently paying for DV certificates, switching to Let's Encrypt saves money with no security tradeoff. DV certificates from paid CAs and Let's Encrypt provide identical encryption and browser trust.

    Migration steps:

  • Install certbot on your server.
  • Run certbot certonly to obtain the new certificate (does not affect the running server).
  • Update your web server configuration to point to the new certificate paths.
  • Reload the web server.
  • Set up auto-renewal with cron.
  • Cancel your paid certificate subscription after verifying everything works.


  • Keep the old certificate active as a fallback until you have confirmed auto-renewal works through at least one cycle (90 days).

    Troubleshooting Renewal Failures



    "Challenge failed for domain"



    The HTTP-01 challenge requires Let's Encrypt to reach http://yourdomain.com/.well-known/acme-challenge/TOKEN. Check:

  • Port 80 is open in your firewall and security groups.
  • Your web server serves the challenge directory.
  • DNS resolves to the correct server IP.


  • "Too many certificates already issued"



    You have hit the rate limit. Wait one week or use a different registered domain. Use --staging for testing.

    "Could not bind to IPv4 or IPv6"



    Another process is using port 80. Stop it, or use the webroot or DNS challenge method instead of standalone.

    Renewal succeeds but the site still shows the old certificate



    The web server was not reloaded. Check that your --deploy-hook is correctly configured and that the web server user has permission to read the new certificate files.

    Certificate Renewal Checklist



  • Certbot installed and certificates obtained.
  • Cron job or systemd timer configured.
  • Deploy hook reloads the web server.
  • certbot renew --dry-run passes.
  • PingCheck monitors certificate expiry as an independent safety net.
  • Cron job execution is monitored with a heartbeat ping.
  • Alert channels tested (verify the Slack/email/webhook actually fires).


  • For the complementary skill of checking certificate expiry on demand, see how to check SSL certificate expiry. For monitoring your API endpoints' uptime and performance alongside SSL, read how to monitor cron jobs.

    Try PingCheck free — no credit card required.