Could we help you? Please click the banners. We are young and desperately need the money
Managing SSL certificates across multiple domains can quickly become overwhelming for Linux server administrators. Expired certificates lead to browser warnings, lost visitor trust, and potential downtime for critical services. For administrators running Virtualmin on Linux servers, maintaining dozens or even hundreds of SSL certificates manually is simply not scalable.
This comprehensive bash script provides a complete solution for automated SSL certificate monitoring and renewal specifically designed for Virtualmin environments. It continuously monitors all SSL-enabled domains, automatically renews certificates approaching expiration using Let's Encrypt, performs DNS resolution checks, and sends detailed email notifications for every issue detected. The script supports modern ECDSA (Elliptic Curve) certificates for enhanced security and performance.
Whether you're managing a small web hosting environment or enterprise-scale infrastructure, this script eliminates the manual overhead of certificate management while ensuring your domains remain secure and accessible.
This automation solution is specifically designed for:
Prerequisites: This script requires a Linux server (Debian/Ubuntu recommended) with Virtualmin installed and configured. It is specifically designed to work with Virtualmin's Let's Encrypt integration and will not function on systems without Virtualmin.
This SSL monitoring script offers comprehensive functionality that goes beyond simple certificate checking:
The script follows a systematic workflow for each SSL-enabled domain in your Virtualmin installation:
The script maintains detailed logs and provides actionable information in every notification, allowing administrators to quickly identify and resolve issues.
Before deploying this script, ensure your Linux server has the required components installed and configured.
The script depends on the following tools, most of which are standard on Debian/Ubuntu systems:
Install the required packages using apt:
sudo apt update
sudo apt install -y openssl mailutils bind9-host coreutils
Verify Virtualmin is installed and accessible:
which virtualmin
# Should output: /usr/sbin/virtualmin
virtualmin list-domains --with-feature ssl --name-only
# Should list your SSL-enabled domains
The script sends notifications via the system's mail command, which typically uses Postfix. For reliable email delivery, especially from cloud servers where port 25 is often blocked, configure Postfix to relay through an SMTP server.
Basic Postfix Configuration (/etc/postfix/main.cf):
# Basic SMTP relay configuration
relayhost = [smtp.yourmailprovider.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
smtp_use_tls = yes
Create SMTP credentials file (/etc/postfix/sasl_passwd):
[smtp.yourmailprovider.com]:587 your-email@domain.tld:your-smtp-password
Generate the password database and restart Postfix:
sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
sudo systemctl restart postfix
# Test email delivery
echo "Test email from SSL monitoring script" | mail -s "Test" your-email@domain.tld
The script includes a comprehensive configuration section at the top of the file. All customizable values are clearly documented and must be adjusted for your environment.
Configure the sender and recipient addresses for notifications:
# Email sender address for notifications
MAIL_SENDER="serveradmin@yourdomain.com"
# Recipients who will receive SSL certificate status notifications
MAIL_RECIPIENTS="serveradmin@yourdomain.com"
You can specify multiple recipients by separating email addresses with spaces or commas, depending on your mail command configuration.
Set how many days before expiration warnings should be triggered:
# Number of days before certificate expiration when warnings should be sent
DAYS_BEFORE_WARNING=10
A value of 10 days is recommended, providing sufficient time for automatic renewal and manual intervention if renewal fails. Let's Encrypt certificates are valid for 90 days, and standard practice is to renew them after 60 days.
Exclude specific domains from monitoring (test domains, placeholder domains, etc.):
# List of domains to exclude from SSL certificate checks
EXCLUDED_DOMAINS="test.example.com placeholder.example.net"
For services outside Virtualmin (mail servers, custom applications, etc.), configure manual certificate file checks:
MANUAL_CERT_CHECKS=(
"path=/etc/ssl/mail-server/cert.pem;name=Mail Server Certificate"
"path=/etc/ssl/custom-app/cert.pem;name=Custom Application Certificate"
)
These certificates will be monitored but cannot be automatically renewed. The script will send notifications when they approach expiration, requiring manual renewal.
Verify or adjust system paths if your distribution uses different locations:
# Log file where SSL check results and errors will be stored
MAILLOG="/var/log/ssl-check-all-hosts.log"
# Path to the mail binary for sending notifications
MAIL_PATH="/usr/bin/mail"
# Path to the Virtualmin binary
VIRTUALMIN_BIN="/usr/sbin/virtualmin"
The script generates two types of email notifications: individual domain reports and a daily summary.
When a certificate is approaching expiration, the script sends a detailed report for that specific domain. Here's what administrators receive:
SSL Certificate Check Report for domain.tld
===========================================
WARNING: Certificate expires in 8 days
Current Certificate Details:
----------------------------
notBefore=Sep 4 15:21:49 2025 GMT
notAfter=Dec 3 15:21:48 2025 GMT
subject=CN = domain.tld
issuer=C = US, O = Let's Encrypt, CN = E8
=== Attempting Certificate Renewal ===
Executing: virtualmin generate-letsencrypt-cert --renew --validate-first --dns-check --ec --domain domain.tld
Checking hostnames for resolvability ..
.. all hostnames can be resolved
Requesting SSL certificate for domain.tld www.domain.tld mail.domain.tld admin.domain.tld from Let's Encrypt ..
.. done for all hostnames
Copying to server configuration ..
.. done
Certificate renewal SUCCESSFUL for domain.tld
=== Post-Renewal Certificate Check ===
New certificate expires in 89 days
notBefore=Nov 25 09:10:55 2025 GMT
notAfter=Feb 23 09:10:54 2026 GMT
subject=CN = domain.tld
issuer=C = US, O = Let's Encrypt, CN = E8
The email subject line clearly indicates the status: [RENEWED], [WARNING], or [ERROR], allowing administrators to prioritize their response.
At the end of each run, the script sends a comprehensive summary showing the status of all domains:
SSL Certificate Check Summary
=============================
Date: 2025-11-25 09:15:32
Warning threshold: 10 days
[OK] domain1.tld - Expires in 45 days
[OK] domain2.tld - Expires in 67 days
[RENEWED] domain3.tld - Was 8 days, now 89 days
[OK] domain4.tld - Expires in 23 days
[WARNING] domain5.tld - Expires in 5 days, renewal FAILED
[ERROR] domain6.tld - DNS resolution failed
[OK] Mail Server Certificate - Expires in 34 days
=============================
Summary Statistics
=============================
Total domains checked: 7
Healthy domains: 5
Domains with issues: 2
Report generated: 2025-11-25 09:15:45
The summary email subject line indicates overall status: [SUCCESS] if all domains are healthy, [WARNING] if any certificates are expiring but renewals succeeded, or [ERROR] if critical issues were detected.
Below is the complete, production-ready bash script. Copy this code, customize the configuration section at the top, and deploy it on your Virtualmin server:
#! /bin/bash
# This script checks SSL certificate validity for all Virtualmin-managed domains
# It performs DNS resolution checks and sends email notifications for expiring certificates
# and any DNS resolution failures
#
# Workflow per domain:
# 1. Verify DNS resolution (send individual email if it fails)
# 2. Check SSL certificate expiry
# 3. If certificate has any issue (DNS error, retrieval error, or expiring soon):
# a. Send individual email notification for that domain
# b. For expiring certificates, attempt automatic renewal first
# 4. At the end, send a summary email with all domain statuses
# File paths and system configurations
# ===================================
# Manual certificate checks configuration
# This array allows you to specify local certificate files that should be checked directly
# Each entry should contain:
# - path: The full path to the certificate file
# - name: A descriptive name for the certificate (used in logs)
# Example entry: ("path=/path/to/cert.pem;name=Description of cert")
# Leave the array empty () if you don't have any manual checks to perform
MANUAL_CERT_CHECKS=(
# Add certificates as needed, one per line:
# "path=/path/to/cert.pem;name=Mail Server Certificate"
# "path=/etc/ssl/custom/cert.pem;name=Custom Service Certificate"
)
# Log file where SSL check results and errors will be stored (summary log)
MAILLOG="/var/log/ssl-check-all-hosts.log"
# Path to the mail binary for sending notifications
MAIL_PATH="/usr/bin/mail"
# Email sender address for notifications
# This can be modified using the 'chfn' command to set the user's full name
# Example: sudo chfn -f "SSL Monitor" serveradmin
MAIL_SENDER="serveradmin@yourdomain.com"
# Recipients who will receive SSL certificate status notifications
MAIL_RECIPIENTS="serveradmin@yourdomain.com"
# Path to the Virtualmin binary for managing virtual servers
VIRTUALMIN_BIN="/usr/sbin/virtualmin"
# Number of days before certificate expiration when warnings should be sent
# Certificates expiring within this window will trigger notification emails
DAYS_BEFORE_WARNING=10
# List of domains to exclude from SSL certificate checks
# Add domain names separated by spaces
# Example: "domain1.com domain2.com domain3.net"
EXCLUDED_DOMAINS="test.example.com placeholder.example.net"
# Status tracking variables
# =======================
# Tracks overall script status - changes to [ERROR] if any issues are found
SUMMARY_STATUS="[SUCCESS]"
# Boolean flag to track if any DNS resolution errors occurred
HAS_DNS_ERROR=false
# Counter for domains with issues
DOMAINS_WITH_ISSUES=0
# Functions
# =========
# Checks if a domain is in the exclusion list
# Parameters:
# $1: Domain name to check
# Returns:
# 0 if domain should be excluded
# 1 if domain should be included
is_excluded() {
local domain="$1"
for excluded in $EXCLUDED_DOMAINS; do
if [ "$domain" = "$excluded" ]; then
return 0
fi
done
return 1
}
# Check SSL certificate expiry for a domain
# Parameters:
# $1: Domain name or IP
# $2: Port (default 443)
# Returns:
# Exit code: 0 on success, 1 on error
# Outputs:
# Days until expiry (negative if expired), or "ERROR" on failure
get_cert_expiry_days() {
local host="$1"
local port="${2:-443}"
local cert_info
local expiry_date
local expiry_epoch
local now_epoch
local days_left
# Get certificate info
cert_info=$(echo | timeout 10 openssl s_client -servername "$host" -connect "${host}:${port}" 2>/dev/null | openssl x509 -noout -dates -subject 2>/dev/null)
if [ -z "$cert_info" ]; then
echo "ERROR"
return 1
fi
# Extract expiry date
expiry_date=$(echo "$cert_info" | grep "notAfter" | cut -d= -f2)
if [ -z "$expiry_date" ]; then
echo "ERROR"
return 1
fi
# Convert to epoch and calculate days
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$days_left"
return 0
}
# Get certificate details for reporting
# Parameters:
# $1: Domain name
# $2: Port (default 443)
get_cert_details() {
local host="$1"
local port="${2:-443}"
echo | timeout 10 openssl s_client -servername "$host" -connect "${host}:${port}" 2>/dev/null | openssl x509 -noout -dates -subject -issuer 2>/dev/null
}
# Check a local certificate file
# Parameters:
# $1: Path to certificate file
# Returns:
# Exit code: 0 on success, 1 on error
# Outputs:
# Days until expiry (negative if expired), or "ERROR" on failure
get_local_cert_expiry_days() {
local cert_path="$1"
local expiry_date
local expiry_epoch
local now_epoch
local days_left
if [ ! -f "$cert_path" ]; then
echo "ERROR"
return 1
fi
expiry_date=$(openssl x509 -noout -enddate -in "$cert_path" 2>/dev/null | cut -d= -f2)
if [ -z "$expiry_date" ]; then
echo "ERROR"
return 1
fi
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$days_left"
return 0
}
# Get local certificate details
# Parameters:
# $1: Path to certificate file
get_local_cert_details() {
local cert_path="$1"
openssl x509 -noout -dates -subject -issuer -in "$cert_path" 2>/dev/null
}
# Attempt to renew SSL certificate for a domain using Virtualmin
# Parameters:
# $1: Domain name to renew certificate for
# Outputs:
# The full output from the virtualmin command
renew_certificate() {
local hostname="$1"
local exit_code
echo "Executing: virtualmin generate-letsencrypt-cert --renew --validate-first --dns-check --ec --domain ${hostname}"
echo ""
${VIRTUALMIN_BIN} generate-letsencrypt-cert --renew --validate-first --dns-check --ec --domain "${hostname}" 2>&1
exit_code=$?
echo ""
if [ $exit_code -eq 0 ]; then
echo "Certificate renewal SUCCESSFUL for ${hostname}"
return 0
else
echo "Certificate renewal FAILED for ${hostname} (exit code: ${exit_code})"
return 1
fi
}
# Send email for a specific domain
# Parameters:
# $1: Domain name
# $2: Status (SUCCESS/WARNING/ERROR)
# $3: Log file path
send_domain_email() {
local domain="$1"
local status="$2"
local log_file="$3"
sudo -u "$MAIL_SENDER" "$MAIL_PATH" -s "[${status}] SSL Certificate Report: ${domain}" \
"$MAIL_RECIPIENTS" < "$log_file" } # Main Script Logic # ================ # Verify required utilities are available if [ ! -x "$MAIL_PATH" ]; then echo "Cannot find [ mail ]. Is it installed?" >&2
exit 1
fi
if ! command -v openssl &> /dev/null; then
echo "Cannot find [ openssl ]. Is it installed?" >&2
exit 1
fi
# Create temporary files for processing
TEMP_DOMAIN_LIST=$(mktemp)
DNS_ERROR_LOG=$(mktemp)
# Initialize the summary log
rm -f "$MAILLOG"
touch "$MAILLOG"
echo "SSL Certificate Check Summary" >> "$MAILLOG"
echo "=============================" >> "$MAILLOG"
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')" >> "$MAILLOG"
echo "Warning threshold: ${DAYS_BEFORE_WARNING} days" >> "$MAILLOG"
echo "" >> "$MAILLOG"
# Retrieve list of SSL-enabled domains from Virtualmin
"${VIRTUALMIN_BIN}" list-domains --with-feature ssl --name-only > "${TEMP_DOMAIN_LIST}"
TOTAL_DOMAINS=0
HEALTHY_DOMAINS=0
# Process each domain individually
while IFS= read -r domain; do
if is_excluded "$domain"; then
continue
fi
((TOTAL_DOMAINS++))
# Verify DNS resolution
if ! host "$domain" >/dev/null 2>&1; then
echo "DNS resolution failed for ${domain}" >> "$DNS_ERROR_LOG"
HAS_DNS_ERROR=true
SUMMARY_STATUS="[ERROR]"
((DOMAINS_WITH_ISSUES++))
# Send email for DNS failure
DOMAIN_LOG=$(mktemp)
echo "SSL Certificate Check Report for ${domain}" > "$DOMAIN_LOG"
echo "===========================================" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "ERROR: DNS resolution failed for ${domain}" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "The domain could not be resolved. Please check:" >> "$DOMAIN_LOG"
echo " - DNS configuration" >> "$DOMAIN_LOG"
echo " - Domain registration status" >> "$DOMAIN_LOG"
send_domain_email "$domain" "ERROR" "$DOMAIN_LOG"
rm -f "$DOMAIN_LOG"
echo "[ERROR] ${domain} - DNS resolution failed" >> "$MAILLOG"
continue
fi
# Check certificate expiry
days_left=$(get_cert_expiry_days "$domain" 443)
if [ "$days_left" = "ERROR" ]; then
# Could not retrieve certificate
SUMMARY_STATUS="[ERROR]"
((DOMAINS_WITH_ISSUES++))
DOMAIN_LOG=$(mktemp)
echo "SSL Certificate Check Report for ${domain}" > "$DOMAIN_LOG"
echo "===========================================" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "ERROR: Could not retrieve SSL certificate for ${domain}" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "Possible causes:" >> "$DOMAIN_LOG"
echo " - No SSL certificate installed" >> "$DOMAIN_LOG"
echo " - Connection timeout" >> "$DOMAIN_LOG"
echo " - Firewall blocking port 443" >> "$DOMAIN_LOG"
send_domain_email "$domain" "ERROR" "$DOMAIN_LOG"
rm -f "$DOMAIN_LOG"
echo "[ERROR] ${domain} - Could not retrieve certificate" >> "$MAILLOG"
continue
fi
if [ "$days_left" -le "$DAYS_BEFORE_WARNING" ]; then
# Certificate is expiring soon or already expired
SUMMARY_STATUS="[WARNING]"
((DOMAINS_WITH_ISSUES++))
# Create per-domain log for email
DOMAIN_LOG=$(mktemp)
echo "SSL Certificate Check Report for ${domain}" > "$DOMAIN_LOG"
echo "===========================================" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
if [ "$days_left" -lt 0 ]; then
echo "CRITICAL: Certificate has EXPIRED (${days_left} days ago)" >> "$DOMAIN_LOG"
else
echo "WARNING: Certificate expires in ${days_left} days" >> "$DOMAIN_LOG"
fi
echo "" >> "$DOMAIN_LOG"
echo "Current Certificate Details:" >> "$DOMAIN_LOG"
echo "----------------------------" >> "$DOMAIN_LOG"
get_cert_details "$domain" 443 >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "=== Attempting Certificate Renewal ===" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
# Attempt renewal and capture output
renewal_output=$(renew_certificate "$domain")
renewal_exit=$?
echo "$renewal_output" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
# Check new certificate status after renewal
if [ $renewal_exit -eq 0 ]; then
echo "=== Post-Renewal Certificate Check ===" >> "$DOMAIN_LOG"
new_days=$(get_cert_expiry_days "$domain" 443)
echo "New certificate expires in ${new_days} days" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
get_cert_details "$domain" 443 >> "$DOMAIN_LOG"
send_domain_email "$domain" "RENEWED" "$DOMAIN_LOG"
echo "[RENEWED] ${domain} - Was ${days_left} days, now ${new_days} days" >> "$MAILLOG"
else
send_domain_email "$domain" "WARNING" "$DOMAIN_LOG"
echo "[WARNING] ${domain} - Expires in ${days_left} days, renewal FAILED" >> "$MAILLOG"
fi
rm -f "$DOMAIN_LOG"
else
# Certificate is healthy
((HEALTHY_DOMAINS++))
echo "[OK] ${domain} - Expires in ${days_left} days" >> "$MAILLOG"
fi
done < "${TEMP_DOMAIN_LIST}" # Process manual certificate checks if [ ${#MANUAL_CERT_CHECKS[@]} -gt 0 ]; then echo "" >> "$MAILLOG"
echo "=== Manual Certificate Checks ===" >> "$MAILLOG"
for cert_entry in "${MANUAL_CERT_CHECKS[@]}"; do
cert_path=$(echo "$cert_entry" | sed 's/^path=\([^;]*\);.*$/\1/')
cert_name=$(echo "$cert_entry" | sed 's/^.*;name=\(.*\)$/\1/')
if [ ! -f "$cert_path" ]; then
echo "[ERROR] ${cert_name} - File not found: ${cert_path}" >> "$MAILLOG"
SUMMARY_STATUS="[ERROR]"
((DOMAINS_WITH_ISSUES++))
continue
fi
days_left=$(get_local_cert_expiry_days "$cert_path")
if [ "$days_left" = "ERROR" ]; then
echo "[ERROR] ${cert_name} - Could not parse certificate" >> "$MAILLOG"
SUMMARY_STATUS="[ERROR]"
((DOMAINS_WITH_ISSUES++))
elif [ "$days_left" -le "$DAYS_BEFORE_WARNING" ]; then
SUMMARY_STATUS="[WARNING]"
((DOMAINS_WITH_ISSUES++))
# Send email for manual cert (cannot auto-renew)
DOMAIN_LOG=$(mktemp)
echo "SSL Certificate Check Report: ${cert_name}" > "$DOMAIN_LOG"
echo "===========================================" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "Certificate file: ${cert_path}" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
if [ "$days_left" -lt 0 ]; then
echo "CRITICAL: Certificate has EXPIRED (${days_left} days ago)" >> "$DOMAIN_LOG"
else
echo "WARNING: Certificate expires in ${days_left} days" >> "$DOMAIN_LOG"
fi
echo "" >> "$DOMAIN_LOG"
echo "Certificate Details:" >> "$DOMAIN_LOG"
echo "-------------------" >> "$DOMAIN_LOG"
get_local_cert_details "$cert_path" >> "$DOMAIN_LOG"
echo "" >> "$DOMAIN_LOG"
echo "NOTE: This is a manually managed certificate. Automatic renewal is not available." >> "$DOMAIN_LOG"
echo "Please renew this certificate manually." >> "$DOMAIN_LOG"
send_domain_email "$cert_name" "WARNING" "$DOMAIN_LOG"
rm -f "$DOMAIN_LOG"
echo "[WARNING] ${cert_name} - Expires in ${days_left} days (manual renewal required)" >> "$MAILLOG"
else
echo "[OK] ${cert_name} - Expires in ${days_left} days" >> "$MAILLOG"
((HEALTHY_DOMAINS++))
fi
done
fi
# Add DNS errors to summary if any occurred
if [ "$HAS_DNS_ERROR" = true ]; then
echo "" >> "$MAILLOG"
echo "=== DNS Resolution Errors ===" >> "$MAILLOG"
cat "$DNS_ERROR_LOG" >> "$MAILLOG"
fi
# Add summary statistics
echo "" >> "$MAILLOG"
echo "=============================" >> "$MAILLOG"
echo "Summary Statistics" >> "$MAILLOG"
echo "=============================" >> "$MAILLOG"
echo "Total domains checked: ${TOTAL_DOMAINS}" >> "$MAILLOG"
echo "Healthy domains: ${HEALTHY_DOMAINS}" >> "$MAILLOG"
echo "Domains with issues: ${DOMAINS_WITH_ISSUES}" >> "$MAILLOG"
echo "" >> "$MAILLOG"
echo "Report generated: $(date '+%Y-%m-%d %H:%M:%S')" >> "$MAILLOG"
# Send summary email
sudo -u "$MAIL_SENDER" "$MAIL_PATH" -s "${SUMMARY_STATUS} Daily SSL Certificate Check Summary" \
"$MAIL_RECIPIENTS" < "$MAILLOG"
# Clean up temporary files
rm -f "$TEMP_DOMAIN_LIST" "$DNS_ERROR_LOG"
After customizing the script configuration, follow these steps to deploy it on your Virtualmin server:
# Create the script file
sudo nano /usr/local/bin/ssl-check-all-hosts
# Paste the complete script code (after customizing configuration)
# Save and exit (Ctrl+X, Y, Enter)
# Make the script executable
sudo chmod +x /usr/local/bin/ssl-check-all-hosts
# Test the script manually
sudo /usr/local/bin/ssl-check-all-hosts
The first run will check all SSL-enabled domains in Virtualmin and send email notifications. Review the emails to ensure formatting and delivery work correctly.
Schedule the script to run daily using cron. This ensures continuous monitoring without manual intervention:
# Edit root's crontab
sudo crontab -e
# Add this line to run daily at 6:00 AM
0 6 * * * /usr/local/bin/ssl-check-all-hosts
# Or run at 3:00 AM for less active hours
0 3 * * * /usr/local/bin/ssl-check-all-hosts
Choose a time during low-traffic hours to minimize any potential impact from certificate renewals requiring service restarts.
The script writes logs to /var/log/ssl-check-all-hosts.log. Configure logrotate to prevent excessive disk usage:
# Create logrotate configuration
sudo nano /etc/logrotate.d/ssl-check-all-hosts
Add this configuration:
/var/log/ssl-check-all-hosts.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
}
This keeps 30 days of log history with compression, providing sufficient audit trail while managing disk space efficiently.
When deploying automation scripts with system-level access, security must be a primary concern:
sudo chown root:root /usr/local/bin/ssl-check-all-hostssudo chmod 640 /var/log/ssl-check-all-hosts.log/etc/postfix/sasl_passwd with 600 permissionsIf you're not receiving email notifications:
sudo systemctl status postfixsudo tail -f /var/log/mail.logecho "test" | mail -s "test" your-email@domain.tld/etc/postfix/sasl_passwdIf domains fail DNS checks unexpectedly:
cat /etc/resolv.confhost domain.tldIf automatic renewals fail:
sudo virtualmin generate-letsencrypt-cert --renew --domain domain.tldIf the cron job doesn't run:
sudo systemctl status cronsudo crontab -lsudo grep ssl-check /var/log/syslogTo maximize the effectiveness of this SSL monitoring solution:
SSL certificate management is a critical but often overlooked aspect of server administration. Expired certificates cause immediate user-facing problems, damage trust, and can impact search engine rankings. For Linux administrators managing multiple domains on Virtualmin, manual certificate monitoring simply doesn't scale.
This comprehensive bash script provides enterprise-grade SSL automation specifically designed for Virtualmin environments. By combining certificate expiry monitoring, automatic Let's Encrypt renewal with modern ECDSA support, DNS validation, and detailed email notifications, it eliminates the manual overhead of SSL management while ensuring your domains remain secure and accessible.
The script runs silently in the background via cron, only alerting administrators when action is required. Successful renewals happen automatically, while failures receive detailed diagnostics to enable rapid resolution. The daily summary provides visibility into your entire SSL certificate infrastructure at a glance.
Deploy this solution today to transform SSL certificate management from a recurring manual task into a fully automated, monitored system. Your future self will thank you when dozens of certificates renew seamlessly without intervention, and you're immediately notified of the rare exceptions that need attention.
For questions, improvements, or to share your deployment experiences, feel free to reach out or contribute enhancements to the script. Effective automation benefits the entire systems administration community.