bash
#!/bin/bash
# SSL证书过期检测脚本
# 支持多域名检测,发送通知到钉钉
# 配置部分
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN"
DAYS_THRESHOLD=30 # 过期前多少天开始警告
RETRY_COUNT=3 # 重试次数
TIMEOUT=10 # 连接超时时间
# 需要检测的域名列表,格式:域名:端口
DOMAINS=(
"example.com:443"
"www.example.com:443"
"api.example.com:443"
"mail.example.com:993"
)
# 钉钉机器人安全设置(可选)
# 如果需要加签,请设置SECRET
DINGTALK_SECRET=""
# 如果需要关键词,请设置KEYWORD
DINGTALK_KEYWORD="证书告警"
# 日志文件
LOG_FILE="/var/log/ssl-checker.log"
# 颜色定义
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 初始化日志
log() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
# 计算日期差异
get_days_diff() {
local expiry_date=$1
local current_date=$(date +%s)
local expiry_seconds=$(date -d "$expiry_date" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$expiry_date" +%s 2>/dev/null)
if [ -z "$expiry_seconds" ]; then
echo "0"
return
fi
local diff_seconds=$((expiry_seconds - current_date))
local diff_days=$((diff_seconds / 86400))
echo $diff_days
}
# 生成钉钉签名(如果需要)
generate_sign() {
if [ -n "$DINGTALK_SECRET" ]; then
local timestamp=$(date +%s%3N)
local string_to_sign="${timestamp}\n${DINGTALK_SECRET}"
local sign=$(echo -en "$string_to_sign" | openssl dgst -sha256 -hmac "$DINGTALK_SECRET" -binary | base64)
echo "×tamp=${timestamp}&sign=${sign}"
else
echo ""
fi
}
# 发送钉钉通知
send_dingtalk_message() {
local domain=$1
local days_left=$2
local expiry_date=$3
local status=$4
# 根据状态设置消息内容
if [ "$status" = "expired" ]; then
local title="🔴 SSL证书已过期"
local color="FF0000"
elif [ "$days_left" -le 7 ]; then
local title="🟠 SSL证书即将过期(${days_left}天后)"
local color="FF9900"
elif [ "$days_left" -le "$DAYS_THRESHOLD" ]; then
local title="🟡 SSL证书即将过期(${days_left}天后)"
local color="FFCC00"
else
local title="🟢 SSL证书正常"
local color="00CC00"
fi
# 构造消息
local message="
## ${title}
**域名:** ${domain}
**过期时间:** ${expiry_date}
**剩余天数:** ${days_left}天
**检测时间:** $(date '+%Y-%m-%d %H:%M:%S')
> 请及时处理证书更新事宜"
# 添加关键词(如果需要)
if [ -n "$DINGTALK_KEYWORD" ]; then
message="${DINGTICK_KEYWORD}\n${message}"
fi
# 构造JSON数据
local data=$(cat <<EOF
{
"msgtype": "markdown",
"markdown": {
"title": "SSL证书检测通知",
"text": "$message"
},
"at": {
"isAtAll": false
}
}
EOF
)
# 生成签名
local sign_params=$(generate_sign)
local webhook_url="${DINGTALK_WEBHOOK}${sign_params}"
# 发送请求
for i in $(seq 1 $RETRY_COUNT); do
local response=$(curl -s -w "%{http_code}" -X POST \
-H "Content-Type: application/json" \
-d "$data" \
"$webhook_url" -o /dev/null)
if [ "$response" = "200" ]; then
log "INFO" "钉钉通知发送成功:${domain}"
return 0
else
log "WARNING" "钉钉通知发送失败,重试第${i}次:${domain}"
sleep 2
fi
done
log "ERROR" "钉钉通知发送失败:${domain}"
return 1
}
# 检测单个域名的证书
check_domain_ssl() {
local domain_info=$1
local domain=$(echo "$domain_info" | cut -d: -f1)
local port=$(echo "$domain_info" | cut -d: -f2)
if [ -z "$port" ]; then
port=443
fi
log "INFO" "开始检测域名:${domain}:${port}"
# 获取证书信息
local cert_info
cert_info=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$cert_info" ]; then
log "ERROR" "无法获取证书信息:${domain}:${port}"
return 1
fi
# 提取过期时间
local expiry_date=$(echo "$cert_info" | grep -i "notAfter" | cut -d= -f2-)
if [ -z "$expiry_date" ]; then
log "ERROR" "无法解析证书过期时间:${domain}:${port}"
return 1
fi
# 计算剩余天数
local days_left=$(get_days_diff "$expiry_date")
# 获取证书其他信息
local issuer=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -issuer 2>/dev/null | cut -d= -f2-)
local subject=$(echo | openssl s_client -servername "$domain" -connect "${domain}:${port}" 2>/dev/null | openssl x509 -noout -subject 2>/dev/null | cut -d= -f2-)
# 输出结果
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}域名:${NC}${domain}:${port}"
echo -e "${GREEN}主题:${NC}${subject}"
echo -e "${GREEN}颁发者:${NC}${issuer}"
echo -e "${GREEN}过期时间:${NC}${expiry_date}"
if [ "$days_left" -le 0 ]; then
echo -e "${RED}状态:证书已过期${NC}"
send_dingtalk_message "${domain}:${port}" 0 "$expiry_date" "expired"
return 2
elif [ "$days_left" -le "$DAYS_THRESHOLD" ]; then
echo -e "${YELLOW}状态:剩余 ${days_left} 天过期${NC}"
send_dingtalk_message "${domain}:${port}" "$days_left" "$expiry_date" "warning"
return 1
else
echo -e "${GREEN}状态:剩余 ${days_left} 天过期${NC}"
send_dingtalk_message "${domain}:${port}" "$days_left" "$expiry_date" "normal"
return 0
fi
}
# 主函数
main() {
log "INFO" "========== SSL证书检测开始 =========="
local expired_count=0
local warning_count=0
local total_count=0
for domain_info in "${DOMAINS[@]}"; do
total_count=$((total_count + 1))
if check_domain_ssl "$domain_info"; then
case $? in
2)
expired_count=$((expired_count + 1))
;;
1)
warning_count=$((warning_count + 1))
;;
esac
fi
sleep 1 # 避免请求过于频繁
done
log "INFO" "检测完成:总共${total_count}个域名,${expired_count}个已过期,${warning_count}个即将过期"
log "INFO" "========== SSL证书检测结束 =========="
# 如果有证书过期,返回非0退出码
if [ "$expired_count" -gt 0 ]; then
exit 2
elif [ "$warning_count" -gt 0 ]; then
exit 1
else
exit 0
fi
}
# 安装openssl检查
check_dependencies() {
if ! command -v openssl &> /dev/null; then
echo -e "${RED}错误:openssl 未安装${NC}"
echo "请使用以下命令安装:"
echo " Ubuntu/Debian: sudo apt-get install openssl"
echo " CentOS/RHEL: sudo yum install openssl"
exit 1
fi
if ! command -v curl &> /dev/null; then
echo -e "${RED}错误:curl 未安装${NC}"
echo "请使用以下命令安装:"
echo " Ubuntu/Debian: sudo apt-get install curl"
echo " CentOS/RHEL: sudo yum install curl"
exit 1
fi
}
# 显示帮助信息
show_help() {
echo "SSL证书过期检测脚本"
echo ""
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -h, --help 显示此帮助信息"
echo " -d, --domain 检测单个域名(格式:domain:port)"
echo " -f, --file 从文件读取域名列表"
echo " -t, --days 设置警告阈值(默认:30天)"
echo " --test 测试钉钉通知"
echo ""
echo "示例:"
echo " $0 -d example.com:443"
echo " $0 -f domains.txt"
echo " $0 -t 15 -d example.com:443"
}
# 测试钉钉通知
test_dingtalk() {
log "INFO" "发送测试通知..."
send_dingtalk_message "test.example.com:443" 365 "Dec 31 23:59:59 2025 GMT" "normal"
if [ $? -eq 0 ]; then
echo -e "${GREEN}测试通知发送成功!${NC}"
else
echo -e "${RED}测试通知发送失败!${NC}"
fi
}
# 参数解析
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-d|--domain)
DOMAINS=("$2")
shift 2
;;
-f|--file)
if [ -f "$2" ]; then
mapfile -t DOMAINS < "$2"
else
echo -e "${RED}错误:文件 $2 不存在${NC}"
exit 1
fi
shift 2
;;
-t|--days)
DAYS_THRESHOLD="$2"
shift 2
;;
--test)
test_dingtalk
exit 0
;;
*)
echo -e "${RED}未知选项:$1${NC}"
show_help
exit 1
;;
esac
done
}
# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
# 检查依赖
check_dependencies
# 解析参数
parse_args "$@"
# 运行主函数
main
fi
- 配置脚本
编辑脚本,修改以下配置:
bash
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN"
DAYS_THRESHOLD=30 # 过期前多少天开始警告
DOMAINS=(
"example.com:443"
"www.example.com:443"
)
- 获取钉钉Webhook
在钉钉群 -> 群设置 -> 智能群助手 -> 添加机器人 -> 自定义
设置机器人名称和安全设置
复制Webhook地址
3. 运行脚本
bash
# 给予执行权限
chmod +x ssl_checker.sh
# 检测所有配置的域名
./ssl_checker.sh
# 检测单个域名
./ssl_checker.sh -d example.com:443
# 从文件读取域名列表
./ssl_checker.sh -f domains.txt
# 设置警告阈值为15天
./ssl_checker.sh -t 15
# 测试钉钉通知
./ssl_checker.sh --test
# 显示帮助
./ssl_checker.sh -h
- 创建域名列表文件
创建 domains.txt:
bash
example.com:443
www.example.com:443
api.example.com:443
mail.example.com:993
- 设置定时任务
bash
# 编辑crontab
crontab -e
# 每天凌晨2点运行
0 2 * * * /path/to/ssl_checker.sh >> /var/log/ssl-checker.log 2>&1
# 每6小时运行一次
0 */6 * * * /path/to/ssl_checker.sh >> /var/log/ssl-checker.log 2>&1
功能特性
多域名检测:支持批量检测多个域名
智能告警:过期/即将过期(可配置阈值)时发送通知
钉钉通知:支持Markdown格式,颜色区分状态
重试机制:网络失败时自动重试
日志记录:详细的操作日志
错误处理:完善的错误检测和处理
参数支持:支持命令行参数
颜色输出:终端彩色输出,便于查看
钉钉通知效果
🔴 红色:证书已过期
🟠 橙色:7天内过期
🟡 黄色:30天内过期
🟢 绿色:证书正常
安全注意事项
将脚本放在安全目录,避免泄露Webhook
定期检查钉钉机器人的安全设置
建议使用加签方式增强安全性
设置适当的文件权限:chmod 700 ssl_checker.sh