A Certbot certificate can renew successfully while the service that uses it keeps serving the old certificate until it is reloaded or synced. Configure a deploy hook when a command must run only after Certbot has obtained a new certificate, not after every scheduled renewal check.

A deploy hook is different from a pre or post renewal hook. Certbot runs deploy hooks once for each successfully issued or renewed certificate, and the hook receives RENEWED_LINEAGE and RENEWED_DOMAINS so the script can act on the certificate that changed.

Use a certificate-specific hook when only one lineage needs the deployment action. Host-wide directory hooks belong under /etc/letsencrypt/renewal-hooks/deploy and are better when the same script should run for every renewed certificate on the server.

Steps to configure a Certbot deploy hook:

  1. Open a terminal on the server that owns the Certbot certificate lineage.
  2. List the saved certificate names.
    $ sudo certbot certificates
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    
    Found the following certs:
      Certificate Name: www.example.com
        Domains: www.example.com example.com
        Expiry Date: 2026-07-25 06:50:00+00:00 (VALID: 42 days)
        Certificate Path: /etc/letsencrypt/live/www.example.com/fullchain.pem
        Private Key Path: /etc/letsencrypt/live/www.example.com/privkey.pem

    Use the value after Certificate Name with --cert-name. If no certificate appears, configure the deploy hook on the host where the certificate was issued or create the certificate before adding the hook.

  3. Create the deploy script in a root-owned executable path.
    $ sudoedit /usr/local/sbin/deploy-certbot-web.sh
    #!/bin/sh
    set -eu
    
    domains=${RENEWED_DOMAINS:-unknown}
    lineage=${RENEWED_LINEAGE:-unknown}
    
    printf 'Deploy hook ran for %s from %s\n' "$domains" "$lineage"
    systemctl reload nginx

    Replace systemctl reload nginx with the deployment command that must run after the renewed certificate is available. Keep the script non-interactive because automatic renewal may run from systemd or cron without a terminal.

  4. Make the deploy script executable.
    $ sudo chmod 755 /usr/local/sbin/deploy-certbot-web.sh
  5. Test the script directly with sample deploy-hook variables.
    $ sudo RENEWED_DOMAINS="www.example.com example.com" \
      RENEWED_LINEAGE="/etc/letsencrypt/live/www.example.com" \
      /usr/local/sbin/deploy-certbot-web.sh
    Deploy hook ran for www.example.com example.com from /etc/letsencrypt/live/www.example.com

    Fix script, permission, service, or reload errors before attaching the script to Certbot. A failing hook prints an error, but the hook failure is not the same as a failed renewal.

  6. Attach the deploy hook to the selected certificate renewal configuration.
    $ sudo certbot reconfigure \
      --cert-name www.example.com \
      --deploy-hook /usr/local/sbin/deploy-certbot-web.sh \
      --run-deploy-hooks

    certbot reconfigure updates the saved renewal configuration for one certificate name. --run-deploy-hooks makes the test path run deploy hooks if the dry-run renewal succeeds, and the hook uses the current active certificate files rather than the temporary staging certificate.

  7. Run a deploy-hook renewal test for the same certificate.
    $ sudo certbot renew --cert-name www.example.com --dry-run --run-deploy-hooks
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    
    Processing /etc/letsencrypt/renewal/www.example.com.conf
    
    Simulating renewal of an existing certificate for www.example.com and example.com
    Deploy hook ran for www.example.com example.com from /etc/letsencrypt/live/www.example.com
    
    Congratulations, all simulated renewals succeeded:
      /etc/letsencrypt/live/www.example.com/fullchain.pem (success)

    During --dry-run, deploy hooks run only when --run-deploy-hooks is included. A host without a saved renewal configuration for the certificate cannot prove the hook with this command.

  8. Let the normal renewal schedule call the deploy hook after the dry run succeeds.
    $ sudo certbot renew
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    No renewals were attempted.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    A normal renewal check may do nothing when certificates are not near expiry. The deploy hook runs after Certbot actually issues or renews the matching certificate.