Let’s Encrypt provides free, automated TLS certificates that are trusted by all major browsers. Certbot is the official ACME client that handles certificate issuance and renewal against the Let’s Encrypt CA. On RHEL 9, Certbot is available through the EPEL repository, making integration with Nginx and Apache straightforward. This tutorial walks through installing Certbot, obtaining certificates, and configuring fully automated renewals using systemd timers.

Prerequisites

  • RHEL 9 server with a public IP address
  • A registered domain name with DNS A records pointing to the server
  • Nginx or Apache installed and serving HTTP traffic
  • Port 80 and 443 open in firewalld
  • EPEL repository enabled (dnf install -y epel-release)

Step 1 — Install Certbot and the Web Server Plugin

With EPEL enabled, install Certbot and the appropriate plugin for your web server. The Nginx plugin automates the configuration of TLS directives, while the Apache plugin performs similar tasks for httpd. Install both here so you can choose based on your stack.

dnf install -y certbot python3-certbot-nginx python3-certbot-apache

Verify the installation:

certbot --version

Step 2 — Obtain a Certificate Using the Nginx Plugin

Run Certbot with the --nginx flag to automatically obtain and install the certificate. Replace example.com with your actual domain. Certbot will modify your Nginx configuration to add the necessary TLS directives and redirect HTTP to HTTPS.

certbot --nginx -d example.com -d www.example.com

You will be prompted for an email address for renewal notices and asked to agree to the Let’s Encrypt Terms of Service. After successful issuance, Certbot updates your Nginx server block and reloads the service. For Apache, substitute --apache for --nginx.

Step 3 — Understand the /etc/letsencrypt/ Directory Structure

Certbot stores all certificate files and account data under /etc/letsencrypt/. The key subdirectories are:

  • live/example.com/ — symlinks to the current active certificate set: cert.pem, chain.pem, fullchain.pem, privkey.pem
  • archive/example.com/ — all past and current certificate files by version number
  • renewal/example.com.conf — renewal configuration including the authenticator plugin and domain list
  • accounts/ — ACME account keys for each CA endpoint
ls -la /etc/letsencrypt/live/example.com/
cat /etc/letsencrypt/renewal/example.com.conf

Step 4 — Verify Automatic Renewal with the Systemd Timer

RHEL 9’s Certbot package ships with a systemd timer that runs certbot renew twice daily. Certificates are only renewed when they are within 30 days of expiry. Check that the timer is active:

systemctl status certbot.timer
systemctl list-timers --all | grep certbot

If the timer is not running, enable and start it:

systemctl enable --now certbot.timer

Simulate a renewal to confirm the configuration is correct without contacting the Let’s Encrypt servers:

certbot renew --dry-run

Step 5 — Configure Pre and Post Renewal Hooks

Renewal hooks allow you to run scripts before and after certificate renewal. Post-hooks are the most common use case — they reload the web server after a new certificate is installed. Create a hook script in the Certbot hooks directory:

cat > /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh << 'EOF'
#!/bin/bash
systemctl reload nginx
EOF

chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh

Pre-hooks (in renewal-hooks/pre/) run before renewal and can be used to temporarily stop a service if needed. Certbot will only execute the post-hook if the certificate was actually renewed.

Step 6 — Standalone Mode and Wildcard Certificates

For servers without a running web server, standalone mode temporarily starts a built-in HTTP server on port 80 to complete the ACME challenge. Stop any service using port 80 first:

systemctl stop nginx
certbot certonly --standalone -d server.example.com
systemctl start nginx

Wildcard certificates (e.g., *.example.com) require a DNS-01 challenge because Let’s Encrypt must verify control of the entire domain. Install the appropriate DNS plugin for your provider (e.g., python3-certbot-dns-cloudflare) and configure credentials, then issue:

certbot certonly 
  --dns-cloudflare 
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini 
  -d "*.example.com" 
  -d example.com

Store DNS provider credentials in a file with permissions 600 to protect them from other users.

Conclusion

You have installed Certbot on RHEL 9, obtained a TLS certificate from Let’s Encrypt, explored the certificate directory structure, confirmed that the systemd renewal timer is active, and configured reload hooks. Your certificates will now renew automatically without manual intervention, keeping your server secure with current TLS credentials.

Next steps: How to Configure IPv6 Dual-Stack Networking on RHEL 9, How to Harden Nginx TLS Configuration on RHEL 9, and How to Set Up HAProxy with TLS Termination on RHEL 9.