SSL Certificate Checker
Monitor SSL certificate expiration dates for multiple domains
Table of Contents
Automatically check SSL certificate expiration dates and alert on certificates expiring soon.
Overview
This script monitors SSL certificates for multiple domains, checking expiration dates, validity, issuer information, and Subject Alternative Names (SANs). It can alert when certificates are expiring within a specified threshold.
Use Case: Certificate lifecycle management, preventing expired certificates, security audits, monitoring multi-domain certificates, compliance reporting.
Platform: Linux/macOS (Bash with openssl) Requirements: openssl command-line tool Execution Time: ~1-2 seconds per domain
The Script
1#!/bin/bash
2
3#
4# SSL Certificate Checker Script
5#
6# Description:
7# Checks SSL certificate expiration dates for multiple domains.
8# Supports reading domains from a file or command line arguments.
9# Alerts on certificates expiring within specified days.
10#
11# Usage:
12# ./ssl-cert-checker.sh [OPTIONS] [domain1 domain2 ...]
13# ./ssl-cert-checker.sh -f domains.txt
14# ./ssl-cert-checker.sh -w 30 -c 7 example.com google.com
15#
16# Options:
17# -f FILE Read domains from file (one per line)
18# -w DAYS Warning threshold in days (default: 30)
19# -c DAYS Critical threshold in days (default: 7)
20# -p PORT Port to check (default: 443)
21# -v Verbose mode (show certificate details)
22# -h Show help
23#
24# Author: glyph.sh
25# Reference: https://glyph.sh/scripts/ssl-cert-checker/
26#
27
28# Default values
29WARNING_DAYS=30
30CRITICAL_DAYS=7
31PORT=443
32VERBOSE=0
33DOMAINS_FILE=""
34DOMAINS=()
35
36# Color codes
37RED='\033[0;31m'
38GREEN='\033[0;32m'
39YELLOW='\033[1;33m'
40CYAN='\033[0;36m'
41BLUE='\033[0;34m'
42NC='\033[0m' # No Color
43
44#
45# Display help
46#
47show_help() {
48 cat << EOF
49SSL Certificate Checker
50
51Usage: $0 [OPTIONS] [domain1 domain2 ...]
52
53Options:
54 -f FILE Read domains from file (one per line)
55 -w DAYS Warning threshold in days (default: 30)
56 -c DAYS Critical threshold in days (default: 7)
57 -p PORT Port to check (default: 443)
58 -v Verbose mode (show certificate details)
59 -h Show help
60
61Examples:
62 $0 example.com google.com
63 $0 -f domains.txt
64 $0 -w 45 -c 14 example.com
65 $0 -v -f domains.txt
66
67Domain file format (one domain per line):
68 example.com
69 google.com
70 github.com
71
72EOF
73 exit 0
74}
75
76#
77# Parse command line arguments
78#
79while getopts "f:w:c:p:vh" opt; do
80 case $opt in
81 f)
82 DOMAINS_FILE="$OPTARG"
83 ;;
84 w)
85 WARNING_DAYS="$OPTARG"
86 ;;
87 c)
88 CRITICAL_DAYS="$OPTARG"
89 ;;
90 p)
91 PORT="$OPTARG"
92 ;;
93 v)
94 VERBOSE=1
95 ;;
96 h)
97 show_help
98 ;;
99 \?)
100 echo "Invalid option: -$OPTARG" >&2
101 exit 1
102 ;;
103 esac
104done
105
106shift $((OPTIND-1))
107
108# Check if openssl is available
109if ! command -v openssl &> /dev/null; then
110 echo -e "${RED}Error: openssl is not installed${NC}"
111 exit 1
112fi
113
114# Get domains from file if specified
115if [ -n "$DOMAINS_FILE" ]; then
116 if [ ! -f "$DOMAINS_FILE" ]; then
117 echo -e "${RED}Error: Domain file '$DOMAINS_FILE' not found${NC}"
118 exit 1
119 fi
120
121 while IFS= read -r domain || [ -n "$domain" ]; do
122 # Skip empty lines and comments
123 [[ -z "$domain" || "$domain" =~ ^[[:space:]]*# ]] && continue
124 # Trim whitespace
125 domain=$(echo "$domain" | xargs)
126 DOMAINS+=("$domain")
127 done < "$DOMAINS_FILE"
128fi
129
130# Add domains from command line arguments
131DOMAINS+=("$@")
132
133# Check if we have any domains
134if [ ${#DOMAINS[@]} -eq 0 ]; then
135 echo -e "${RED}Error: No domains specified${NC}"
136 echo "Use -h for help"
137 exit 1
138fi
139
140#
141# Get certificate information for a domain
142#
143check_certificate() {
144 local domain="$1"
145 local port="$2"
146
147 echo -e "${CYAN}Checking: ${domain}:${port}${NC}"
148
149 # Get certificate information
150 local cert_info
151 cert_info=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -dates -issuer -subject -ext subjectAltName 2>/dev/null)
152
153 if [ -z "$cert_info" ]; then
154 echo -e " ${RED}✗ Failed to retrieve certificate${NC}"
155 echo ""
156 return 1
157 fi
158
159 # Extract dates
160 local not_before
161 local not_after
162 not_before=$(echo "$cert_info" | grep "notBefore" | cut -d= -f2)
163 not_after=$(echo "$cert_info" | grep "notAfter" | cut -d= -f2)
164
165 # Convert expiration date to epoch
166 local exp_epoch
167 if [[ "$OSTYPE" == "darwin"* ]]; then
168 # macOS
169 exp_epoch=$(date -j -f "%b %d %T %Y %Z" "$not_after" +%s 2>/dev/null)
170 else
171 # Linux
172 exp_epoch=$(date -d "$not_after" +%s 2>/dev/null)
173 fi
174
175 if [ -z "$exp_epoch" ]; then
176 echo -e " ${RED}✗ Failed to parse expiration date${NC}"
177 echo ""
178 return 1
179 fi
180
181 # Calculate days until expiration
182 local current_epoch
183 current_epoch=$(date +%s)
184 local seconds_diff=$((exp_epoch - current_epoch))
185 local days_remaining=$((seconds_diff / 86400))
186
187 # Extract issuer and subject
188 local issuer
189 local subject
190 issuer=$(echo "$cert_info" | grep "issuer=" | sed 's/issuer=//')
191 subject=$(echo "$cert_info" | grep "subject=" | sed 's/subject=//')
192
193 # Extract SANs
194 local sans
195 sans=$(echo "$cert_info" | grep -A1 "Subject Alternative Name" | tail -n1 | sed 's/^[[:space:]]*//')
196
197 # Determine status
198 local status_color="$GREEN"
199 local status_icon="✓"
200 local status_text="Valid"
201
202 if [ $days_remaining -lt 0 ]; then
203 status_color="$RED"
204 status_icon="✗"
205 status_text="EXPIRED"
206 elif [ $days_remaining -le $CRITICAL_DAYS ]; then
207 status_color="$RED"
208 status_icon="⚠"
209 status_text="CRITICAL"
210 elif [ $days_remaining -le $WARNING_DAYS ]; then
211 status_color="$YELLOW"
212 status_icon="⚠"
213 status_text="WARNING"
214 fi
215
216 # Display results
217 echo -e " ${status_color}${status_icon} Status: ${status_text}${NC}"
218 echo -e " Expires: ${not_after} (${days_remaining} days)"
219 echo -e " Valid from: ${not_before}"
220
221 # Verbose output
222 if [ $VERBOSE -eq 1 ]; then
223 echo -e " ${BLUE}Issuer:${NC}"
224 echo " $issuer"
225 echo -e " ${BLUE}Subject:${NC}"
226 echo " $subject"
227
228 if [ -n "$sans" ]; then
229 echo -e " ${BLUE}Subject Alternative Names:${NC}"
230 echo " $sans"
231 fi
232 fi
233
234 echo ""
235
236 # Return status code
237 if [ $days_remaining -lt 0 ]; then
238 return 2 # Expired
239 elif [ $days_remaining -le $CRITICAL_DAYS ]; then
240 return 3 # Critical
241 elif [ $days_remaining -le $WARNING_DAYS ]; then
242 return 4 # Warning
243 fi
244
245 return 0 # OK
246}
247
248#
249# Main execution
250#
251echo -e "${CYAN}========================================${NC}"
252echo -e "${CYAN} SSL Certificate Checker${NC}"
253echo -e "${CYAN}========================================${NC}"
254echo -e "Warning threshold: ${WARNING_DAYS} days"
255echo -e "Critical threshold: ${CRITICAL_DAYS} days"
256echo -e "Total domains: ${#DOMAINS[@]}"
257echo ""
258
259# Statistics
260TOTAL=0
261OK=0
262WARNING=0
263CRITICAL=0
264EXPIRED=0
265FAILED=0
266
267# Check each domain
268for domain in "${DOMAINS[@]}"; do
269 ((TOTAL++))
270 check_certificate "$domain" "$PORT"
271 status=$?
272
273 case $status in
274 0) ((OK++)) ;;
275 2) ((EXPIRED++)) ;;
276 3) ((CRITICAL++)) ;;
277 4) ((WARNING++)) ;;
278 *) ((FAILED++)) ;;
279 esac
280done
281
282#
283# Summary
284#
285echo -e "${CYAN}========================================${NC}"
286echo -e "${CYAN} Summary${NC}"
287echo -e "${CYAN}========================================${NC}"
288echo -e "Total domains checked: ${TOTAL}"
289echo -e "${GREEN}✓ Valid: ${OK}${NC}"
290
291if [ $WARNING -gt 0 ]; then
292 echo -e "${YELLOW}⚠ Warning: ${WARNING}${NC}"
293fi
294
295if [ $CRITICAL -gt 0 ]; then
296 echo -e "${RED}⚠ Critical: ${CRITICAL}${NC}"
297fi
298
299if [ $EXPIRED -gt 0 ]; then
300 echo -e "${RED}✗ Expired: ${EXPIRED}${NC}"
301fi
302
303if [ $FAILED -gt 0 ]; then
304 echo -e "${RED}✗ Failed: ${FAILED}${NC}"
305fi
306
307echo ""
308
309# Exit with appropriate code
310if [ $EXPIRED -gt 0 ] || [ $FAILED -gt 0 ]; then
311 exit 2
312elif [ $CRITICAL -gt 0 ]; then
313 exit 3
314elif [ $WARNING -gt 0 ]; then
315 exit 4
316fi
317
318exit 0Usage
Basic Usage
Check single domain:
1./ssl-cert-checker.sh example.comCheck multiple domains:
1./ssl-cert-checker.sh example.com google.com github.comUsing a Domain File
Create a file with domains (one per line):
1cat > domains.txt << EOF
2example.com
3google.com
4github.com
5cloudflare.com
6EOF
7
8./ssl-cert-checker.sh -f domains.txtCustom Thresholds
Set custom warning and critical thresholds:
1# Warn at 45 days, critical at 14 days
2./ssl-cert-checker.sh -w 45 -c 14 example.comVerbose Mode
Show detailed certificate information:
1./ssl-cert-checker.sh -v example.comCustom Port
Check certificate on non-standard port:
1./ssl-cert-checker.sh -p 8443 example.comSample Output
Normal Output
========================================
SSL Certificate Checker
========================================
Warning threshold: 30 days
Critical threshold: 7 days
Total domains: 3
Checking: example.com:443
✓ Status: Valid
Expires: Dec 15 23:59:59 2025 GMT (43 days)
Valid from: Sep 15 00:00:00 2025 GMT
Checking: expiring-soon.com:443
⚠ Status: WARNING
Expires: Nov 20 23:59:59 2025 GMT (18 days)
Valid from: Nov 20 00:00:00 2024 GMT
Checking: critical-cert.com:443
⚠ Status: CRITICAL
Expires: Nov 8 23:59:59 2025 GMT (6 days)
Valid from: Nov 8 00:00:00 2024 GMT
========================================
Summary
========================================
Total domains checked: 3
✓ Valid: 1
⚠ Warning: 1
⚠ Critical: 1Verbose Output
Checking: example.com:443
✓ Status: Valid
Expires: Dec 15 23:59:59 2025 GMT (43 days)
Valid from: Sep 15 00:00:00 2025 GMT
Issuer:
C=US, O=Let's Encrypt, CN=R3
Subject:
CN=example.com
Subject Alternative Names:
DNS:example.com, DNS:www.example.comDomain File Format
Create a text file with one domain per line:
1# Production domains
2example.com
3www.example.com
4api.example.com
5
6# Staging domains
7staging.example.com
8
9# Partner domains
10partner1.com
11partner2.comComments (lines starting with #) and empty lines are ignored.
Scheduled Monitoring
Cron Job (Daily Check)
1# Run daily at 9 AM, email results if issues found
20 9 * * * /path/to/ssl-cert-checker.sh -f /path/to/domains.txt || mail -s "SSL Certificate Alert" admin@example.com < /path/to/output.logWeekly Report
1# Generate weekly report every Monday at 8 AM
20 8 * * 1 /path/to/ssl-cert-checker.sh -v -f /path/to/domains.txt > /var/log/ssl-weekly-report.log 2>&1Email Notifications
1#!/bin/bash
2OUTPUT=$(/path/to/ssl-cert-checker.sh -f /path/to/domains.txt)
3STATUS=$?
4
5if [ $STATUS -ne 0 ]; then
6 echo "$OUTPUT" | mail -s "SSL Certificate Alert - Action Required" admin@example.com
7fiExit Codes
The script uses exit codes to indicate status:
0- All certificates valid2- One or more expired certificates or connection failures3- One or more certificates in critical threshold4- One or more certificates in warning threshold
This makes it easy to integrate with monitoring systems.
Integration Examples
Nagios/Icinga
1#!/bin/bash
2# Nagios plugin wrapper
3OUTPUT=$(/path/to/ssl-cert-checker.sh -f /etc/nagios/domains.txt)
4STATUS=$?
5
6echo "$OUTPUT"
7exit $STATUSZabbix
1#!/bin/bash
2# Return 1 for OK, 0 for problems
3/path/to/ssl-cert-checker.sh -f /etc/zabbix/domains.txt > /dev/null 2>&1
4if [ $? -eq 0 ]; then
5 echo 1
6else
7 echo 0
8fiSlack Notification
1#!/bin/bash
2OUTPUT=$(/path/to/ssl-cert-checker.sh -f /path/to/domains.txt)
3STATUS=$?
4
5if [ $STATUS -ne 0 ]; then
6 curl -X POST -H 'Content-type: application/json' \
7 --data "{\"text\":\"SSL Certificate Alert:\n\`\`\`$OUTPUT\`\`\`\"}" \
8 YOUR_SLACK_WEBHOOK_URL
9fiAdvanced Usage
Check Multiple Ports
1#!/bin/bash
2DOMAIN="example.com"
3PORTS=(443 8443 9443)
4
5for port in "${PORTS[@]}"; do
6 ./ssl-cert-checker.sh -p $port $DOMAIN
7doneExport to CSV
1#!/bin/bash
2# Modified script output to CSV format
3echo "Domain,Status,Days Remaining,Expiration Date" > report.csv
4./ssl-cert-checker.sh -f domains.txt | grep -E "(Checking|Status|Expires)" | \
5 awk '/Checking/{domain=$2} /Status/{status=$3} /Expires/{print domain","status","$NF}' >> report.csvJSON Output (Modified Script)
Add this function for JSON output:
1# Add to script for JSON output
2if [ "$JSON_OUTPUT" = "1" ]; then
3 echo "{"
4 echo " \"domain\": \"$domain\","
5 echo " \"status\": \"$status_text\","
6 echo " \"days_remaining\": $days_remaining,"
7 echo " \"expires\": \"$not_after\","
8 echo " \"issuer\": \"$issuer\""
9 echo "}"
10fiTroubleshooting
“openssl: command not found”
Install OpenSSL:
1# Ubuntu/Debian
2sudo apt-get install openssl
3
4# RHEL/CentOS/Rocky
5sudo yum install openssl
6
7# macOS
8brew install opensslConnection Timeout
Some servers may have strict firewall rules. Try:
1# Increase timeout (modify script)
2timeout 10 openssl s_client -servername "$domain" -connect "${domain}:${port}"SNI Issues
If you get certificate mismatches, ensure SNI is enabled (it is by default with -servername).
macOS Date Parsing Issues
The script handles both macOS and Linux date formats. If you still have issues:
1# Check date command
2date --version # GNU date (Linux)
3date -j # BSD date (macOS)Certificate Details Explained
- Not Before: Certificate validity start date
- Not After: Certificate expiration date
- Issuer: Certificate Authority that issued the certificate
- Subject: Domain(s) the certificate is issued to
- SANs: Subject Alternative Names (additional domains covered)
See Also
Download
1curl -O https://glyph.sh/scripts/ssl-cert-checker.sh
2chmod +x ssl-cert-checker.sh
3./ssl-cert-checker.sh -h