Set up automated SSL certificate management for Nginx using Let's Encrypt and Certbot. Configure automatic certificate renewal, implement SSL security hardening, and monitor certificate health for production websites.
Prerequisites
- Root or sudo access to the server
- Domain name pointed to server
- Nginx installed and running
- Port 80 and 443 accessible
What this solves
Manual SSL certificate management creates security risks and service interruptions when certificates expire unexpectedly. This tutorial implements automated SSL certificate management for Nginx using Let's Encrypt and Certbot, with automatic renewal and monitoring to ensure your websites maintain HTTPS without manual intervention.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all packages.
sudo apt update && sudo apt upgrade -y
Install Nginx if not already present
Ensure Nginx is installed and running before configuring SSL certificates.
sudo apt install -y nginx
sudo systemctl enable --now nginx
Install Certbot and Nginx plugin
Install Certbot with the Nginx plugin to enable automatic certificate installation and configuration.
sudo apt install -y certbot python3-certbot-nginx
Configure Nginx virtual host
Create a basic Nginx virtual host configuration for your domain. This initial configuration uses HTTP only, which Certbot will automatically upgrade to HTTPS.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
location /.well-known/acme-challenge/ {
root /var/www/html;
}
}
Enable the virtual host
Create a symbolic link to enable the virtual host and test the Nginx configuration.
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Create web root directory
Create the web root directory and set proper permissions for the web server to serve files.
sudo mkdir -p /var/www/html
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
Create test content
Add a simple test page to verify the web server is working before obtaining SSL certificates.
SSL Test Page
SSL Certificate Automation Test
This page will be served over HTTPS after certificate installation.
SSL certificate automation configuration
Obtain SSL certificates with Certbot
Use Certbot to automatically obtain and install SSL certificates for your domain. The nginx plugin will automatically modify your Nginx configuration to use HTTPS.
sudo certbot --nginx -d example.com -d www.example.com --email admin@example.com --agree-tos --non-interactive
Verify SSL certificate installation
Check that Certbot successfully modified your Nginx configuration and installed the SSL certificate.
sudo certbot certificates
sudo nginx -t
Configure automatic renewal with systemd timer
Create a systemd timer for automatic certificate renewal instead of relying on cron. This provides better logging and monitoring capabilities.
[Unit]
Description=Certbot SSL certificate renewal
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --nginx --post-hook "systemctl reload nginx"
User=root
Create systemd timer configuration
Configure the timer to run certificate renewal checks twice daily to ensure certificates are renewed well before expiration.
[Unit]
Description=Run Certbot SSL renewal twice daily
Requires=certbot-renewal.service
[Timer]
OnCalendar=--* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
Enable and start the renewal timer
Enable the systemd timer to ensure automatic certificate renewal continues after system reboots.
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renewal.timer
sudo systemctl status certbot-renewal.timer
SSL security hardening
Configure SSL security parameters
Create a shared SSL configuration file with modern security settings to be included in all virtual hosts.
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header X-XSS-Protection "1; mode=block";
Update virtual host with security parameters
Modify your virtual host configuration to include the SSL security parameters and ensure proper certificate handling.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/html;
index index.html index.htm;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/snippets/ssl-params.conf;
location / {
try_files $uri $uri/ =404;
}
location /.well-known/acme-challenge/ {
root /var/www/html;
}
}
Test and reload Nginx configuration
Verify the updated configuration and reload Nginx to apply the SSL security hardening.
sudo nginx -t
sudo systemctl reload nginx
Certificate monitoring and alerting
Create certificate monitoring script
Develop a monitoring script to check certificate expiration dates and send alerts if certificates are nearing expiration.
#!/bin/bash
SSL Certificate Expiry Monitor
DOMAINS=("example.com" "www.example.com")
WARN_DAYS=30
ALERT_EMAIL="admin@example.com"
LOGFILE="/var/log/ssl-monitor.log"
for DOMAIN in "${DOMAINS[@]}"; do
EXPIRY_DATE=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -dates | grep notAfter | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( (EXPIRY_EPOCH - CURRENT_EPOCH) / 86400 ))
echo "$(date): $DOMAIN expires in $DAYS_UNTIL_EXPIRY days" >> $LOGFILE
if [ $DAYS_UNTIL_EXPIRY -le $WARN_DAYS ]; then
echo "SSL certificate for $DOMAIN expires in $DAYS_UNTIL_EXPIRY days" | mail -s "SSL Certificate Expiry Warning" $ALERT_EMAIL
fi
done
Make monitoring script executable
Set proper permissions for the monitoring script and create the log file.
sudo chmod +x /usr/local/bin/check-ssl-expiry.sh
sudo touch /var/log/ssl-monitor.log
sudo chown root:root /var/log/ssl-monitor.log
sudo chmod 644 /var/log/ssl-monitor.log
Schedule certificate monitoring
Create a cron job to run the certificate monitoring script daily and alert on certificates nearing expiration.
sudo crontab -e
Add this line to run the monitoring script daily at 8 AM:
0 8 * /usr/local/bin/check-ssl-expiry.sh
Configure log rotation for monitoring
Set up log rotation for the SSL monitoring log to prevent disk space issues.
/var/log/ssl-monitor.log {
weekly
missingok
rotate 4
compress
delaycompress
notifempty
copytruncate
}
Advanced renewal configuration
Create renewal hooks directory
Set up directories for pre and post renewal hooks to customize certificate renewal behavior.
sudo mkdir -p /etc/letsencrypt/renewal-hooks/pre
sudo mkdir -p /etc/letsencrypt/renewal-hooks/post
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
Create post-renewal hook
Add a post-renewal hook to reload Nginx and log successful renewals for monitoring purposes.
#!/bin/bash
Post-renewal hook to reload Nginx and log renewal
echo "$(date): SSL certificates renewed successfully" >> /var/log/ssl-renewal.log
Reload Nginx to use new certificates
systemctl reload nginx
Verify Nginx is still running
if systemctl is-active --quiet nginx; then
echo "$(date): Nginx reloaded successfully after certificate renewal" >> /var/log/ssl-renewal.log
else
echo "$(date): ERROR: Nginx failed to reload after certificate renewal" >> /var/log/ssl-renewal.log
systemctl status nginx >> /var/log/ssl-renewal.log
fi
Make renewal hook executable
Set proper permissions for the renewal hook script.
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
sudo touch /var/log/ssl-renewal.log
sudo chown root:root /var/log/ssl-renewal.log
sudo chmod 644 /var/log/ssl-renewal.log
Test automatic renewal
Perform a dry run of the certificate renewal process to ensure everything works correctly.
sudo certbot renew --dry-run
sudo systemctl status certbot-renewal.timer
Verify your setup
Test your SSL certificate automation setup with these verification commands:
# Check certificate status and expiration
sudo certbot certificates
Verify SSL configuration
curl -I https://example.com
Test renewal timer status
sudo systemctl status certbot-renewal.timer
sudo systemctl list-timers certbot-renewal.timer
Check SSL security grade (external tool)
curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com&publish=off"
Verify certificate chain
openssl s_client -connect example.com:443 -servername example.com
Check monitoring script
sudo /usr/local/bin/check-ssl-expiry.sh
Review logs
sudo tail -f /var/log/ssl-monitor.log
sudo tail -f /var/log/ssl-renewal.log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Certificate request fails | Domain not pointing to server | Verify DNS records point to server IP |
| Renewal timer not running | Service disabled or failed | sudo systemctl enable --now certbot-renewal.timer |
| Nginx fails to reload after renewal | Configuration syntax error | sudo nginx -t to check configuration |
| SSL certificate not trusted | Incomplete certificate chain | Use fullchain.pem instead of cert.pem |
| Rate limit exceeded | Too many certificate requests | Wait for rate limit reset, use staging environment for testing |
| Port 80 blocked | Firewall blocking HTTP | Open port 80: sudo ufw allow 80/tcp |
| Monitoring script fails | Missing mail command | Install mailutils: sudo apt install mailutils |
| Permission denied on webroot | Incorrect ownership/permissions | sudo chown -R www-data:www-data /var/www/html |
Next steps
- Configure Nginx reverse proxy with SSL termination for load balancing multiple backend servers
- Set up NGINX rate limiting and security headers for DDoS protection to enhance security
- Monitor nginx performance with Prometheus and Grafana for comprehensive observability
- Configure Nginx SSL monitoring with Prometheus alerts for automated certificate monitoring
- Implement Nginx SSL certificate backup automation for disaster recovery
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
DOMAIN=""
EMAIL=""
WEBROOT="/var/www/html"
# Usage message
usage() {
echo "Usage: $0 -d DOMAIN [-e EMAIL] [-w WEBROOT]"
echo " -d DOMAIN : Domain name (required)"
echo " -e EMAIL : Email for Let's Encrypt notifications (optional)"
echo " -w WEBROOT : Web root directory (default: /var/www/html)"
exit 1
}
# Parse command line arguments
while getopts "d:e:w:h" opt; do
case $opt in
d) DOMAIN="$OPTARG" ;;
e) EMAIL="$OPTARG" ;;
w) WEBROOT="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Check required arguments
if [ -z "$DOMAIN" ]; then
echo -e "${RED}Error: Domain name is required${NC}"
usage
fi
# Check prerequisites
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Error: This script must be run as root${NC}"
exit 1
fi
# Cleanup function for rollback
cleanup() {
echo -e "${YELLOW}Cleaning up on error...${NC}"
systemctl stop nginx 2>/dev/null || true
rm -f "/etc/nginx/sites-available/$DOMAIN" 2>/dev/null || true
rm -f "/etc/nginx/sites-enabled/$DOMAIN" 2>/dev/null || true
rm -f "/etc/nginx/conf.d/$DOMAIN.conf" 2>/dev/null || true
}
trap cleanup ERR
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
WEB_USER="www-data"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
WEB_USER="nginx"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
WEB_USER="nginx"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
echo -e "${GREEN}Starting Nginx SSL automation setup for $DOMAIN${NC}"
# Step 1: Update system packages
echo -e "${GREEN}[1/10] Updating system packages...${NC}"
$PKG_UPDATE
# Step 2: Install Nginx
echo -e "${GREEN}[2/10] Installing Nginx...${NC}"
$PKG_INSTALL nginx
systemctl enable nginx
systemctl start nginx
# Step 3: Install Certbot and Nginx plugin
echo -e "${GREEN}[3/10] Installing Certbot and Nginx plugin...${NC}"
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL certbot python3-certbot-nginx
else
$PKG_INSTALL certbot python3-certbot-nginx
fi
# Step 4: Configure firewall
echo -e "${GREEN}[4/10] Configuring firewall...${NC}"
if command -v ufw >/dev/null 2>&1; then
ufw allow 'Nginx Full' 2>/dev/null || true
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-service=http --add-service=https 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
fi
# Step 5: Create web root directory
echo -e "${GREEN}[5/10] Creating web root directory...${NC}"
mkdir -p "$WEBROOT"
chown -R "$WEB_USER:$WEB_USER" "$WEBROOT"
chmod -R 755 "$WEBROOT"
# Step 6: Create test content
echo -e "${GREEN}[6/10] Creating test content...${NC}"
cat > "$WEBROOT/index.html" << EOF
<!DOCTYPE html>
<html>
<head>
<title>SSL Test Page</title>
</head>
<body>
<h1>SSL Certificate Automation Test</h1>
<p>This page will be served over HTTPS after certificate installation.</p>
</body>
</html>
EOF
chown "$WEB_USER:$WEB_USER" "$WEBROOT/index.html"
chmod 644 "$WEBROOT/index.html"
# Step 7: Configure Nginx virtual host
echo -e "${GREEN}[7/10] Configuring Nginx virtual host...${NC}"
if [ -n "$NGINX_ENABLED_DIR" ]; then
# Debian-based systems
CONFIG_FILE="$NGINX_SITES_DIR/$DOMAIN"
cat > "$CONFIG_FILE" << EOF
server {
listen 80;
listen [::]:80;
server_name $DOMAIN www.$DOMAIN;
root $WEBROOT;
index index.html index.htm;
location / {
try_files \$uri \$uri/ =404;
}
location /.well-known/acme-challenge/ {
root $WEBROOT;
}
}
EOF
ln -sf "$CONFIG_FILE" "$NGINX_ENABLED_DIR/"
else
# RHEL-based systems
CONFIG_FILE="$NGINX_SITES_DIR/$DOMAIN.conf"
cat > "$CONFIG_FILE" << EOF
server {
listen 80;
listen [::]:80;
server_name $DOMAIN www.$DOMAIN;
root $WEBROOT;
index index.html index.htm;
location / {
try_files \$uri \$uri/ =404;
}
location /.well-known/acme-challenge/ {
root $WEBROOT;
}
}
EOF
fi
chmod 644 "$CONFIG_FILE"
# Step 8: Test and reload Nginx
echo -e "${GREEN}[8/10] Testing and reloading Nginx configuration...${NC}"
nginx -t
systemctl reload nginx
# Step 9: Obtain SSL certificates
echo -e "${GREEN}[9/10] Obtaining SSL certificates with Certbot...${NC}"
CERTBOT_CMD="certbot --nginx -d $DOMAIN -d www.$DOMAIN --agree-tos --non-interactive"
if [ -n "$EMAIL" ]; then
CERTBOT_CMD="$CERTBOT_CMD --email $EMAIL"
else
CERTBOT_CMD="$CERTBOT_CMD --register-unsafely-without-email"
fi
$CERTBOT_CMD
# Step 10: Configure automatic renewal
echo -e "${GREEN}[10/10] Configuring automatic renewal...${NC}"
cat > /etc/systemd/system/certbot-renewal.service << EOF
[Unit]
Description=Certbot SSL certificate renewal
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --nginx --post-hook "systemctl reload nginx"
User=root
EOF
cat > /etc/systemd/system/certbot-renewal.timer << EOF
[Unit]
Description=Run Certbot SSL renewal twice daily
Requires=certbot-renewal.service
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
EOF
chmod 644 /etc/systemd/system/certbot-renewal.service
chmod 644 /etc/systemd/system/certbot-renewal.timer
systemctl daemon-reload
systemctl enable certbot-renewal.timer
systemctl start certbot-renewal.timer
# Verification
echo -e "${GREEN}Verifying installation...${NC}"
nginx -t
certbot certificates
systemctl status nginx --no-pager -l
echo -e "${GREEN}SSL automation setup completed successfully!${NC}"
echo -e "${GREEN}Your site should now be accessible at https://$DOMAIN${NC}"
echo -e "${YELLOW}Certificate auto-renewal is configured to run twice daily${NC}"
Review the script before running. Execute with: bash install.sh