How to Set Up Automatic Certificate Renewal with Certbot on Fedora

Enable the certbot.timer systemd unit on Fedora to automatically renew SSL certificates before they expire.

You just secured your site with a Let's Encrypt certificate

The browser shows a green padlock. Your monitoring dashboard turns green. You breathe easy. Then three months later, the certificate expires. Your visitors see a red warning screen. Your uptime alerts fire at 2 AM. You could set a calendar reminder, but calendar reminders fail. Fedora handles this automatically if you wire up the right timer.

What is actually happening

Let's Encrypt issues certificates valid for exactly ninety days. That short lifespan is intentional. It forces automation and limits the damage window if a private key gets compromised. Certbot talks to the Let's Encrypt API, proves you control the domain, and drops the certificate files on disk. The renewal process is identical to the initial issuance, except Certbot only acts when a certificate has fewer than thirty days left.

A systemd timer wakes up twice a day, checks the expiration dates, and runs the renewal command. The timer does not renew certificates on a fixed schedule. It renews them when they are close to expiring. If the system is off or sleeping during those windows, systemd catches up as soon as the machine wakes. The timer unit is a lightweight wrapper around the certbot renew command. It does not require cron, external scripts, or manual scheduling.

Run systemctl status certbot.timer before you guess. The timer state tells you exactly when it last fired and when it will fire next.

Set up the renewal timer

Fedora ships Certbot with a preconfigured systemd timer. You only need to enable and start it. The timer is disabled by default because some users prefer manual control or use a different ACME client. Enabling it is a one-time operation.

Here is how to activate the automatic renewal process and make it survive reboots.

# Enable the timer so systemd starts it on boot
sudo systemctl enable certbot.timer
# Start the timer immediately without waiting for reboot
sudo systemctl start certbot.timer
# Verify the timer is active and show the next scheduled run
sudo systemctl status certbot.timer

The timer configuration lives in /usr/lib/systemd/system/certbot.timer. That directory ships with the package. Never edit files in /usr/lib/. System updates will overwrite your changes. If you need to adjust the schedule, create an override in /etc/systemd/system/certbot.timer.d/. Fedora's package manager trusts /etc/ for user modifications and leaves /usr/lib/ untouched.

Here is how to check the exact schedule the timer uses.

# Show the timer unit file to see the OnCalendar directive
systemctl cat certbot.timer
# List all active timers and their next trigger times
systemctl list-timers --all

The OnCalendar=*-*-* 00,12:00:00 line tells systemd to run the job at midnight and noon. The RandomizedDelaySec=1h line spreads the load across the Let's Encrypt infrastructure. Certbot itself checks the expiration date and only renews if fewer than thirty days remain. The timer fires twice daily regardless of whether renewal is needed. That is by design. It keeps the automation simple and predictable.

Enable the timer once. Let systemd handle the rest.

Verify the automation works

You do not want to wait ninety days to find out the renewal failed. Test the renewal path immediately after setup. Certbot provides a dry-run mode that simulates the entire ACME handshake without modifying your disk or contacting the production API.

Here is how to simulate a renewal and confirm the pipeline works end to end.

# Run a dry run to test renewal without touching real certificates
sudo certbot renew --dry-run
# Check the journal for the timer unit to see past executions
journalctl -xeu certbot.timer
# Show the exact command the timer executes
systemctl cat certbot.service

A successful dry run prints Congratulations, all simulated renewals succeeded. The journalctl -xeu certbot.timer command shows the timer trigger history and any errors from previous runs. The -x flag adds explanatory context to journal lines. The -e flag jumps to the end. Most sysadmins type this combination muscle-memory style. Read the actual error before guessing.

If the dry run fails, the output will point to a specific plugin or configuration file. Fix the underlying issue before retesting. Do not force a live renewal until the dry run passes. A botched renewal can overwrite your working certificate with a failed one.

Run the dry run after every configuration change. Trust the simulation over hope.

Common pitfalls and what the error looks like

Certbot renewal fails for three main reasons. The web server is not running. The challenge directory has wrong permissions. SELinux blocks the ACME validation request. Each failure prints a distinct error message. Match the output to the fix.

The certbot renew --dry-run command will refuse to proceed and print Failed authorization procedure. example.com (http-01): urn:ietf:params:acme:error:unauthorized. The unauthorized error means Let's Encrypt could not reach the challenge file. Your web server is likely not listening on port 80, or the document root is misconfigured. Restart your web server and verify it serves static files before retrying.

If you see Permission denied: '/var/www/html/.well-known/acme-challenge' during renewal, your web server process cannot write to the challenge directory. Certbot needs write access to place the validation token. Fix the ownership and SELinux context on the webroot.

Here is how to repair the challenge directory permissions and restore the correct SELinux context.

# Ensure the web server user owns the challenge directory
sudo chown -R apache:apache /var/www/html/.well-known
# Restore the default SELinux context for web content
sudo restorecon -Rv /var/www/html/.well-known
# Verify the context matches httpd_sys_content_t
ls -lZ /var/www/html/.well-known

SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. The restorecon command resets file contexts to the package defaults. Manual chcon commands drift across updates. Trust the package manager.

If your firewall blocks port 80, the HTTP-01 challenge will fail silently from Let's Encrypt's perspective. The ACME server cannot reach your machine. Open port 80 temporarily for validation, or switch to the DNS-01 challenge if you are behind a reverse proxy. Run firewall-cmd --reload after every rule change. Otherwise the runtime config and the persistent config diverge.

Check the web server logs and the firewall rules before blaming Certbot. Half the time the symptom is upstream.

Choose the right plugin for your stack

Certbot supports multiple plugins. Each plugin handles the ACME challenge differently. Pick the one that matches your current setup.

Use the webroot plugin when you already have a web server serving static files and you want Certbot to place challenge tokens in a specific directory. Use the nginx or apache plugin when you want Certbot to rewrite your server configuration automatically and manage virtual hosts. Use the standalone plugin when you have no web server running and can stop other services temporarily to bind port 80. Use the DNS plugin when you are behind a cloudflare proxy or need wildcard certificates that require DNS-01 validation.

The webroot plugin is the most portable. It works with any HTTP server that can serve static files. The nginx and apache plugins are convenient but tie Certbot to a specific web server configuration. The standalone plugin is useful for testing but requires stopping your production web server during renewal. The DNS plugin requires API credentials for your domain registrar or cloud provider.

Match the plugin to your architecture. Do not force a plugin that requires services you do not run.

Where to go next