HTTPS is no longer optional — browsers mark HTTP sites as “Not Secure”, search engines penalise them in rankings, and many modern browser APIs (service workers, geolocation, camera access) require a secure context. Let’s Encrypt provides free, automated, and trusted SSL/TLS certificates via the ACME protocol, and Certbot is the recommended ACME client that handles domain validation, certificate issuance, Nginx configuration updates, and automatic renewals. On RHEL 9, Certbot is available from the EPEL repository and integrates cleanly with Nginx and firewalld. This guide covers installing Certbot on RHEL 9, obtaining a certificate with the Nginx plugin, setting up automatic renewal, and configuring Nginx for TLS 1.3, HSTS, and OCSP stapling.
Prerequisites
- Nginx installed and running on RHEL 9
- A domain name with a DNS A record pointing to your server’s public IP
- Port 80 open in firewalld (required for the HTTP-01 challenge)
- Port 443 open in firewalld (for HTTPS)
Step 1 — Install EPEL and Certbot
# Enable EPEL
dnf install -y epel-release
# Install Certbot and the Nginx plugin
dnf install -y certbot python3-certbot-nginx
Step 2 — Obtain a Certificate
The Nginx plugin (--nginx) automatically edits your Nginx server block to add the SSL directives and HTTP-to-HTTPS redirect:
# Replace example.com with your actual domain
certbot --nginx -d example.com -d www.example.com
--agree-tos --no-eff-email -m [email protected]
Certbot will:
- Perform an HTTP-01 challenge to verify domain ownership
- Generate a certificate and save it under
/etc/letsencrypt/live/example.com/ - Modify your Nginx server block to add SSL directives
- Add an HTTP-to-HTTPS redirect
Alternatively, obtain the certificate only (without modifying Nginx) using certonly:
certbot certonly --nginx -d example.com -d www.example.com
--agree-tos --no-eff-email -m [email protected]
Step 3 — Verify the Certificate
# List all certificates
certbot certificates
# Verify HTTPS is working
curl -I https://example.com
# Check certificate details
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null |
openssl x509 -noout -dates -subject
Step 4 — Manually Configure Nginx for TLS 1.3 and Security Headers
After Certbot creates the certificate, review and harden the SSL configuration:
vi /etc/nginx/conf.d/example.com.conf
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com/html;
# Certificate files (generated by Certbot)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# TLS 1.3 only (+ TLS 1.2 for compatibility)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy "no-referrer-when-downgrade";
location / {
try_files $uri $uri/ =404;
}
}
nginx -t && systemctl reload nginx
Step 5 — Set Up Automatic Certificate Renewal
Certbot installs a systemd timer that renews certificates automatically when they are within 30 days of expiry:
# Verify the timer is active
systemctl status certbot-renew.timer
# Test renewal (dry run — does not actually renew)
certbot renew --dry-run
# Force renewal now (for testing)
certbot renew --force-renewal
Certbot reloads Nginx automatically after renewal via a deploy hook. Verify it is configured:
cat /etc/letsencrypt/renewal/example.com.conf | grep deploy_hook
Step 6 — Test SSL Configuration
# Test from the command line
openssl s_client -connect example.com:443 -tls1_3 -brief 2>/dev/null
# Check SSL Labs grade (requires internet access to check remotely)
# Visit: https://www.ssllabs.com/ssltest/analyze.html?d=example.com
# Verify HSTS header
curl -si https://example.com | grep -i strict
Conclusion
Your Nginx server on RHEL 9 is now serving HTTPS with a free Let’s Encrypt certificate, TLS 1.2/1.3, OCSP stapling, HSTS, and automatic renewal via a systemd timer. SSL Labs should give an A or A+ rating with this configuration. Certificates renew automatically 30 days before expiry — no manual intervention required.
Next steps: How to Secure Apache with Let’s Encrypt on RHEL 9, How to Configure Nginx as a Reverse Proxy on RHEL 9, and How to Harden Nginx: Security Headers, TLS 1.3 and OCSP Stapling on RHEL 9.