Uptime & SSL14 min read

    How to renew an SSL certificate (step-by-step)

    By DanaServer Monitoring & Linux
    Share

    The honest version of "how to renew an SSL certificate" is that you don't renew it — certificates aren't extended, they're replaced. The Certificate Authority issues a brand-new certificate (with a new serial number, new validity dates, sometimes a new key pair) covering the same hostnames, and you swap it for the old one on every server that serves TLS for that domain. The old certificate keeps working until its notAfter date, and the new one takes over from the moment your service reloads.

    The exact mechanics depend on three things: which CA issued the cert (Let's Encrypt, a commercial CA, or a cloud provider), where the cert lives (Nginx, Apache, IIS, a load balancer, a CDN), and whether automation is already in place. This guide walks through every common path, with the exact commands, and ends with how to make the next renewal a non-event.


    Before you start: identify what you have

    Run this against the live endpoint — not against the file on disk — to confirm what is actually being served:

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

    Typical output:

    issuer=C = US, O = Let's Encrypt, CN = R3
    subject=CN = example.com
    notBefore=Feb 10 08:14:32 2026 GMT
    notAfter=May 11 08:14:31 2026 GMT
    X509v3 Subject Alternative Name:
        DNS:example.com, DNS:www.example.com
    

    The fields you need:

    • IssuerLet's Encrypt, DigiCert, Sectigo, Cloudflare Inc ECC CA-3, Amazon, etc. This decides which renewal path you take.
    • Subject + SAN — every hostname covered by the cert. The renewal must cover the same set of hostnames (or a superset) or you'll break clients hitting one of the names.
    • notAfter — the exact expiry. Plan to replace the cert at least 14 days before this; renew earlier if your release cadence is slow.
    • Subject Public Key Info — RSA vs ECDSA. If you re-issue with a different key type, older clients (some embedded, some IoT) may break.

    If your cert is from Let's Encrypt → Path A. From a commercial CA (DigiCert, Sectigo, GoDaddy, GlobalSign) → Path B. From a cloud provider (ACM, GCP, Cloudflare) → Path C.


    Path A — Let's Encrypt (with certbot)

    Let's Encrypt certificates are valid for 90 days. The intended workflow is automated renewal — certbot should already be installing a systemd timer or cron job that renews when the cert is within 30 days of expiry. You should rarely have to do this by hand.

    Check whether automation is already running

    sudo systemctl list-timers | grep certbot
    # certbot.timer    Sat 2026-05-10 03:42:11 UTC  ...
    

    Or for cron:

    sudo crontab -l | grep certbot
    ls /etc/cron.d/ | grep certbot
    

    If you see a timer or cron entry, automation exists. Skip to verification:

    sudo certbot renew --dry-run
    

    A dry run executes the renewal flow against the staging API and reports what would happen. If it succeeds, the next real renewal will succeed too — no manual action needed.

    Manual renewal

    To force a renewal now (for instance, if expiry is close and the timer hasn't fired yet):

    sudo certbot renew
    

    certbot renew is idempotent — it will only actually renew certificates that are within 30 days of expiry. To renew earlier, add --force-renewal (use sparingly; Let's Encrypt has rate limits, and --force-renewal counts against them).

    After renewal, certbot needs to reload your web server so it picks up the new files. The default install on Debian/Ubuntu/RHEL puts a deploy hook in place; verify with:

    ls /etc/letsencrypt/renewal-hooks/deploy/
    # nginx-reload   ← or whatever you installed
    

    If the directory is empty, add one:

    sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx <<'EOF'
    #!/bin/sh
    nginx -t && systemctl reload nginx
    EOF
    sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
    

    That single hook is the difference between "the new cert is on disk" and "the new cert is being served". A surprising number of expired-cert incidents are renewals that succeeded but were never reloaded.

    DNS-01 vs HTTP-01

    If your domain is behind a CDN, a private network, or you need a wildcard (*.example.com), the HTTP-01 challenge won't work. Use DNS-01:

    sudo certbot certonly --manual --preferred-challenges dns \
        -d example.com -d '*.example.com'
    

    For unattended automation with DNS-01, use a DNS plugin (certbot-dns-cloudflare, certbot-dns-route53, certbot-dns-google, etc.) so the TXT record is created and removed automatically.

    Common certbot renewal failures

    • The following errors were reported by the server: Domain: example.com Type: connection — Let's Encrypt couldn't reach http://example.com/.well-known/acme-challenge/.... Check that port 80 is open and unredirected for ACME paths, and that no rule in Nginx returns 301 to HTTPS for /.well-known/acme-challenge/. Carve out an exception:
      location ^~ /.well-known/acme-challenge/ {
          root /var/www/letsencrypt;
          allow all;
      }
      
    • Rate limit exceeded — Let's Encrypt limits 5 duplicate certs per week, 50 certs per registered domain per week, etc. Use --staging and --dry-run while iterating.
    • Account key not found — usually a permissions problem on /etc/letsencrypt/accounts/. Don't change those permissions; instead, run certbot as the user that owns them (root by default).

    Path B — Commercial CA (DigiCert, Sectigo, GoDaddy, GlobalSign, …)

    Commercial CAs issue certificates valid for up to one year (the industry maximum has been 398 days since 2020). The renewal flow is more manual and has four phases: generate a CSR, place the order, validate domain control, install.

    1. Generate a new CSR (and key)

    For most renewals, generate a new private key rather than reusing the old one — it's a free improvement to your security posture. Skip this advice only if your platform mandates key reuse (some HSM-backed setups do).

    openssl req -new -newkey rsa:2048 -nodes \
        -keyout example.com.key \
        -out example.com.csr \
        -subj "/C=US/ST=California/L=San Francisco/O=Example Inc/CN=example.com" \
        -addext "subjectAltName = DNS:example.com,DNS:www.example.com"
    

    For ECDSA (smaller, faster, supported by every modern client):

    openssl ecparam -name prime256v1 -genkey -noout -out example.com.key
    openssl req -new -key example.com.key \
        -out example.com.csr \
        -subj "/CN=example.com" \
        -addext "subjectAltName = DNS:example.com,DNS:www.example.com"
    

    Verify the CSR before submitting. A typo here means you re-do everything:

    openssl req -in example.com.csr -noout -text | grep -E 'Subject|DNS:'
    

    Treat example.com.key as a secret — same handling as a database password. Mode 600, owned by root, never committed to a repo.

    2. Submit and validate

    Paste the CSR contents (cat example.com.csr) into your CA's renewal page. The CA will:

    1. Read the SANs from the CSR.
    2. Ask you to prove control of every domain in the SAN list. Validation methods:
      • HTTP file validation — they give you a token; you place a file at http://example.com/.well-known/pki-validation/<token>.txt.
      • DNS TXT validation — they give you a TXT record to add (e.g. _dnsauth.example.com).
      • Email validation — they email an admin address (admin@, webmaster@, postmaster@, or the WHOIS contact). Avoid this for production; the address must exist and receive mail.

    DNS validation is usually the cleanest for renewals — no traffic on port 80 required, and you can pre-stage the TXT records.

    3. Download the issued certificate

    The CA returns:

    • The leaf certificate for your domain (example.com.crt).
    • The intermediate certificate(s) chaining to a trusted root.
    • Sometimes a "bundle" file that's leaf + intermediates concatenated (often named fullchain.pem).

    Always serve the full chain, not just the leaf. Browsers tolerate missing intermediates because their root stores have heuristics; many API clients, mobile apps, and IoT devices do not. A "works in browser, fails in curl" error after renewal is almost always a missing intermediate.

    Build the chain file yourself if the CA gives you separate files:

    cat example.com.crt intermediate.crt > fullchain.pem
    

    The order matters: leaf first, then intermediates from closest-to-leaf to closest-to-root.

    4. Verify before deploying

    openssl verify -CAfile intermediate.crt example.com.crt
    # example.com.crt: OK
    

    And confirm the new key matches the new cert:

    diff <(openssl rsa -modulus -noout -in example.com.key) \
         <(openssl x509 -modulus -noout -in example.com.crt)
    

    (For ECDSA, replace rsa with ec and use pubkey extraction.) If they don't match, you've mixed up files — do not deploy.

    A more thorough check is covered in the companion article: How to check and verify SSL certificates with OpenSSL.


    Path C — Cloud-managed certificates (ACM, GCP, Cloudflare)

    If your cert is issued and served by a cloud provider, "renewal" is mostly a nudge — the provider handles the issuance and rotation as long as the prerequisites stay in place.

    AWS Certificate Manager (ACM)

    Public ACM certificates are renewed by AWS automatically up to 60 days before expiry, if domain validation can still be performed. For DNS-validated certs, that means the CNAME records ACM created at issuance must still exist in your DNS. If you deleted them, the renewal fails silently and you discover it 60 days later.

    Verify the validation records still exist for every domain on the cert:

    aws acm describe-certificate --certificate-arn <arn> \
        --query 'Certificate.DomainValidationOptions'
    

    Each entry should show ValidationStatus: SUCCESS. If any are FAILED or PENDING_VALIDATION, re-add the missing CNAMEs.

    Google Cloud Managed Certificates

    Used with HTTPS Load Balancers. Auto-renew is on by default; the cert resource shows Status: ACTIVE when healthy. If it shows PROVISIONING for more than an hour, the load balancer hasn't seen traffic on the right port or the domain isn't pointed at the LB IP.

    Cloudflare Universal SSL / Advanced Certificates

    Universal SSL renews automatically. Advanced Certificates auto-renew 30 days before expiry, contingent on:

    • The domain still being on Cloudflare.
    • The hostnames in the cert still resolving to Cloudflare IPs.
    • For DNS-validated certs, the validation records (_acme-challenge.<host>) still being present.

    Check the SSL/TLS → Edge Certificates panel for renewal status.


    Installing the new certificate

    Nginx

    Drop the new files in place (preserving permissions and ownership) and reload:

    sudo cp fullchain.pem /etc/nginx/ssl/example.com.crt
    sudo cp example.com.key /etc/nginx/ssl/example.com.key
    sudo chmod 600 /etc/nginx/ssl/example.com.key
    sudo chown root:root /etc/nginx/ssl/example.com.key
    
    sudo nginx -t && sudo systemctl reload nginx
    

    The relevant Nginx config:

    server {
        listen 443 ssl http2;
        server_name example.com www.example.com;
    
        ssl_certificate     /etc/nginx/ssl/example.com.crt;
        ssl_certificate_key /etc/nginx/ssl/example.com.key;
        # ...
    }
    

    reload (not restart) keeps existing connections open while new connections pick up the new cert.

    Apache

    SSLCertificateFile      /etc/ssl/certs/example.com.crt
    SSLCertificateKeyFile   /etc/ssl/private/example.com.key
    SSLCertificateChainFile /etc/ssl/certs/example.com.chain.crt
    
    sudo apachectl configtest && sudo systemctl reload apache2     # Debian/Ubuntu
    sudo apachectl configtest && sudo systemctl reload httpd       # RHEL family
    

    Modern Apache (2.4.8+) accepts the full chain as the SSLCertificateFile and ignores SSLCertificateChainFile — older docs still mention the latter, but you can collapse to one file.

    IIS (Windows Server)

    The clean path is via certutil (or PowerShell). For PFX-based renewals:

    # Import the new PFX into the local machine store
    Import-PfxCertificate -FilePath C:\certs\example.com.pfx `
        -CertStoreLocation Cert:\LocalMachine\My `
        -Password (Read-Host -AsSecureString)
    
    # Get the new thumbprint
    Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -match 'example.com' }
    
    # Bind it to the HTTPS site (replace site name + thumbprint)
    $thumb = '<NEW_THUMBPRINT_HERE>'
    New-Item -Path "IIS:\SslBindings\0.0.0.0!443" -Thumbprint $thumb -SSLFlags 0
    

    If the binding already exists, remove the old one first (Remove-Item -Path "IIS:\SslBindings\0.0.0.0!443") before re-creating.

    Load balancers (AWS ALB, GCP LB, HAProxy)

    • AWS ALB / NLB — import the new cert into ACM (or upload to IAM if not using ACM), then update the listener to use the new ARN. Old ARN can be deleted after a few minutes.
    • GCP LB — create a new SSL certificate resource, attach to the target proxy, then delete the old resource.
    • HAProxy — replace the PEM file (combined leaf + chain + key in that order), then haproxy -c -f /etc/haproxy/haproxy.cfg && systemctl reload haproxy.

    Validate the renewal

    The five checks worth running every time:

    # 1. Live cert dates and SANs
    echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
        | openssl x509 -noout -dates -ext subjectAltName
    
    # 2. Full chain delivery
    echo | openssl s_client -servername example.com -connect example.com:443 -showcerts 2>/dev/null \
        | grep -c 'BEGIN CERTIFICATE'
    # Should be ≥ 2 (leaf + at least one intermediate)
    
    # 3. Chain trusts to a system root
    curl -v --resolve example.com:443:<server-ip> https://example.com/ 2>&1 | grep -E 'subject|issuer|SSL connection'
    
    # 4. From a different network (catches host-file overrides and split-DNS surprises)
    curl -I https://example.com/
    
    # 5. Browser-grade test (covers HSTS, OCSP stapling, cipher suites)
    # Use https://www.ssllabs.com/ssltest/ for a one-shot deep test.
    

    For the most thorough verification (chain order, OCSP, SCTs), see How to check and verify SSL certificates with OpenSSL.


    Set up auto-renewal — properly

    The thing that prevents the next expiry is the first thing to set up after this one.

    • Let's Encryptsystemctl enable --now certbot.timer (or the cron entry). Add a deploy hook that reloads your web server. Verify monthly with certbot renew --dry-run.
    • Commercial CA — most CAs offer ACME endpoints now (DigiCert, Sectigo, GlobalSign, ZeroSSL all do). Switching the renewal automation to ACME makes renewals a non-event. If you're stuck on portal-based renewals, set a calendar reminder 30 days before expiry on the renewal owner's actual calendar (not a shared inbox no one reads).
    • Cloud-managed — confirm DNS validation records are stable, version-controlled, and not at risk of being removed by a different team's automation.
    • Always combine automation with monitoring. Auto-renewal can fail silently (rate limit, DNS change, deploy hook missing) and you'll only find out when traffic breaks. Monitoring catches the failed renewal before expiry, while there's still time.

    Catch the next expiry before users do

    The painful failure mode for an SSL certificate isn't "I forgot to renew" — it's "the auto-renewal silently failed three weeks ago and the cert expires at 03:00 on a Sunday". By the time the support inbox lights up with browser warnings, every customer hit by it has lost trust.

    The right safety net is continuous SSL certificate monitoring: a check that opens a TLS connection to your endpoint every few minutes, parses the served certificate, and pages you when expiry is closer than a configured threshold. Specifically, the things worth alerting on:

    • Days to expiry — multi-stage warning, e.g. info at 30 days, warning at 14 days, critical at 7 days.
    • Chain validity — full chain trusts to a public root, in the right order.
    • Hostname coverage — the served cert's SANs still cover every hostname you serve from this endpoint.
    • TLS version and cipher — depreciation of TLS 1.0/1.1, weak ciphers, etc.

    Xitoring's SSL certificate monitoring does all four — it pings the endpoint from multiple regions, validates the chain end to end, alerts on expiry against your chosen thresholds, and flags hostname or chain mismatches the moment a renewal goes sideways. Pair it with HTTPS uptime monitoring so the same dashboard tells you whether the site is up and whether the cert serving it is healthy.

    If the next renewal is a Let's Encrypt one, the monitor will tell you within minutes if the deploy hook didn't fire and the new cert never reached Nginx — exactly the silent-failure case that causes most expiry incidents. (For background on why this matters, see Monitoring SSL certificates for expiration.)


    Summary

    To renew an SSL certificate, in order:

    1. Identify what you have. openssl s_client | openssl x509 -noout -issuer -subject -dates -ext subjectAltName against the live endpoint. The issuer decides the path; the SANs decide the scope.
    2. Renew via the right path. Let's Encrypt → certbot renew (with deploy hook). Commercial CA → new CSR + new key → submit → validate → download full chain. Cloud-managed → confirm DNS validation records are still present.
    3. Always serve the full chain, not just the leaf. Build it explicitly: cat leaf.crt intermediate.crt > fullchain.pem.
    4. Verify the key matches the cert before deploying — openssl rsa -modulus (or ec) on both files and diff them.
    5. Reload, don't restart. nginx -t && systemctl reload nginx (Apache: apachectl configtest && systemctl reload). Reload preserves in-flight connections.
    6. Validate the live endpoint after deploy — dates, SANs, chain length ≥ 2, trust from a clean network.
    7. Set up auto-renewal the same day. ACME endpoints exist for most CAs now; use them.
    8. Wire SSL expiry into monitoring. Auto-renewal can fail silently; monitoring is the only thing that catches a broken renewal before it becomes an expired cert.

    A renewal done well is invisible — users see no certificate warning, no momentary downtime, no chain error in curl. With deploy hooks, ACME automation, and continuous expiry monitoring in place, the next renewal becomes one less thing on the on-call list. The work to set it up once is small; the cost of getting it wrong is whatever fraction of your traffic uses a client that takes browser warnings seriously.