【Shell】利用 OpenSSL 脚本自动化检测 HTTPS 域名证书过期时间与告警实战

admin / Shell / 已更新 2026-09-22 / 2253 字 · 约 16 分钟 / 579 次阅读 /

在当今互联网业务中,HTTPS 已经成为全网 Web 服务与 API 接口的绝对标配。然而,无论是初创团队还是大型互联网企业,因 SSL/TLS 证书过期未及时续签而导致的服务不可用、浏览器强拦截以及用户信任危机等生产事故屡见不鲜。

人工记忆和表格记录往往存在遗漏风险,最可靠的防范机制是:建立自动化巡检与监控告警体系,当证书剩余有效期不足 30 天时自动触发企业微信/钉钉/邮件告警

本文深度解析利用原生 openssl 命令行远程探测 HTTPS 证书剩余天数的核心原理,并提供单域名提取、批量巡检、多并发加速及监控系统集成的完整工程落地方案。


一、OpenSSL 探测原理与核心参数速查

无需登录部署证书的目标服务器,仅凭客户端即可通过 TLS 握手直接获取目标域名的公开证书信息。核心命令流水线如下:

echo | openssl s_client -servername <域名> -connect <域名>:443 2>/dev/null | openssl x509 -noout -enddate

核心参数解析对照表

核心组件 / 参数 功能描述 生产必备原因
echo \| 向客户端输入一个换行符 s_client 默认保持交互式等待,输入换行可使其在获取证书后主动断开退出
timeout 5 强制设定连接超时上限 避免目标网络不可达或握手阻塞导致脚本永远挂起卡死
-servername 启用 SNI (Server Name Indication) 至关重要! 现代服务器一个 IP 往往绑定多个域名,不加此参数会导致握手返回默认泛域名证书
-connect 指定探测目标的主机名与端口 默认 Web 端口为 443
2>/dev/null 屏蔽底层握手详细过程与错误日志 过滤冗余调试信息,保持标准输出纯净
openssl x509 X.509 证书专用解析工具 专门从流中抓取证书体并进行结构化解码
-noout -enddate 仅输出证书截止时间 notAfter 屏蔽证书公钥 Base64 编码,只返回格式如 notAfter=Dec 31 23:59:59 2026 GMT

[!WARNING] SNI 陷阱警告: 很多开发者在写检测脚本时习惯只写 -connect $domain:443,而遗漏了 -servername $domain。在反向代理(如 Nginx / CDN / K8s Ingress)承载多租户或多证书场景下,缺少 SNI 会导致拿到的证书与目标域名不匹配,造成严重的误报或漏报!


二、单域名秒级提取一行流实战

利用 openssl 获取到证书的截止时间字符串后,需要将其换算为 UNIX 时间戳,并与当前时间戳对比计算剩余天数。

1. Linux (GNU date) 一行流

在主流 Linux 发行版(CentOS, Ubuntu, Debian, Alpine 等)上,date -d 支持直接解析 GMT 时间格式:

DOMAIN="www.baidu.com"

# 1. 抓取截止时间字符串 (格式: notAfter=May 17 06:48:47 2026 GMT)
END_DATE=$(echo | timeout 5 openssl s_client -servername "$DOMAIN" -connect "$DOMAIN":443 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d '=' -f 2)

# 2. 将截止时间与当前时间转换为秒级时间戳并计算天数
END_TIMESTAMP=$(date +%s -d "$END_DATE")
NOW_TIMESTAMP=$(date +%s)
DAYS_LEFT=$(( (END_TIMESTAMP - NOW_TIMESTAMP) / 86400 ))

echo "域名 $DOMAIN 证书剩余天数: $DAYS_LEFT 天"

2. macOS (BSD date) 跨平台兼容

macOS 原生使用的 BSD date 不支持 -d 参数,需使用 -j -f

# macOS 兼容提取时间戳
END_TIMESTAMP=$(date -j -f "%b %d %T %Y %Z" "$END_DATE" +%s)

三、批量多域名巡检脚本实现 (基础串行版)

在日常运维中,我们通常将需要监控的域名清单单独存放在一个配置文件中,便于日常扩容与解耦维护。

1. 域名清单配置文件 domain_list.txt

www.baidu.com
www.github.com
api.x7lab.cn:443

2. 巡检脚本 check_ssl_expire.sh

#!/usr/bin/env bash
# check_ssl_expire.sh - 批量检测 SSL 证书过期剩余天数

DOMAINS_FILE="${1:-domain_list.txt}"
WARN_DAYS=30

if [ ! -f "$DOMAINS_FILE" ]; then
    echo "错误: 域名清单文件 $DOMAINS_FILE 不存在!"
    exit 1
fi

echo "==================== SSL 证书有效期巡检报告 ===================="
printf "%-30s | %-12s | %-10s\n" "域名" "剩余天数" "状态"
echo "----------------------------------------------------------------"

while IFS= read -r line || [ -n "$line" ]; do
    # 过滤注释与空行
    [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue

    # 拆分域名与端口 (默认 443)
    DOMAIN=$(echo "$line" | cut -d ':' -f 1 | xargs)
    PORT=$(echo "$line" | cut -s -d ':' -f 2 | xargs)
    PORT="${PORT:-443}"

    # 抓取证书过期截止时间
    RAW_DATE=$(echo | timeout 5 openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:$PORT" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d '=' -f 2)

    if [ -z "$RAW_DATE" ]; then
        printf "%-30s | %-12s | %-10s\n" "$DOMAIN:$PORT" "探测失败" "ERROR"
        continue
    fi

    # 计算剩余天数
    END_TS=$(date +%s -d "$RAW_DATE")
    NOW_TS=$(date +%s)
    DAYS=$(( (END_TS - NOW_TS) / 86400 ))

    if [ "$DAYS" -le "$WARN_DAYS" ]; then
        STATUS="⚠️ 紧急临期"
    else
        STATUS="✅ 正常"
    fi

    printf "%-30s | %-12s | %-10s\n" "$DOMAIN:$PORT" "${DAYS} 天" "$STATUS"
done < "$DOMAINS_FILE"

四、并发加速巡检与进程控制 (进阶健壮版)

当监控列表中的域名多达数十上百个时,串行检测如果遇到网络握手超时的域名,整体执行时间会大幅延长。因此引入后台并发子进程 + wait 同步屏障是极佳的提速手段。

[!TIP] 并发安全性建议: 避免使用硬编码 sleep 盲等,改用 Bash 原生 wait 指令等待所有后台子进程执行收敛,既保证数据完整性,又兼顾最高执行速度。

#!/usr/bin/env bash
# check_ssl_concurrent.sh - 多并发高性能证书过期巡检

DOMAINS_FILE="${1:-domain_list.txt}"
TEMP_RESULT=$(mktemp /tmp/ssl_check_XXXXXX.txt)
WARN_DAYS=30

# 单域名检测函数
check_domain() {
    local domain=$1
    local port=${2:-443}

    local raw_date
    raw_date=$(echo | timeout 3 openssl s_client -servername "$domain" -connect "$domain:$port" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d '=' -f 2)

    if [ -z "$raw_date" ]; then
        echo "$domain:$port|FAILED|0" >> "$TEMP_RESULT"
        return
    fi

    local end_ts now_ts days
    end_ts=$(date +%s -d "$raw_date")
    now_ts=$(date +%s)
    days=$(( (end_ts - now_ts) / 86400 ))

    echo "$domain:$port|SUCCESS|$days" >> "$TEMP_RESULT"
}

# 并发拉起巡检进程
while IFS= read -r line || [ -n "$line" ]; do
    [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
    domain=$(echo "$line" | cut -d ':' -f 1 | xargs)
    port=$(echo "$line" | cut -s -d ':' -f 2 | xargs)
    port="${port:-443}"

    check_domain "$domain" "$port" &
done < "$DOMAINS_FILE"

# 优雅等待所有后台并发子任务完成
wait

# 格式化输出汇总
echo "==================== 并发巡检汇总结果 ===================="
while IFS='|' read -r target status days; do
    if [ "$status" = "SUCCESS" ]; then
        if [ "$days" -le "$WARN_DAYS" ]; then
            echo -e "[\033[31m告警\033[0m] $target 仅剩 $days 天,请立即续签!"
        else
            echo -e "[\033[32m正常\033[0m] $target 剩余 $days 天"
        fi
    else
        echo -e "[\033[33m失败\033[0m] $target 证书探测失败,请检查网络或端口"
    fi
done < "$TEMP_RESULT"

# 清理临时文件
rm -f "$TEMP_RESULT"

五、企业级监控告警集成方案

将脚本输出与主流监控平台联动,才能真正实现无人值守:

1. Zabbix 自动化发现与低级别发现 (LLD)

  • 将脚本部署为 Zabbix Agent 的 UserParametertext UserParameter=ssl.cert.expiry[*],/usr/local/bin/check_ssl_single.sh $1
  • 在 Zabbix Server 上配置触发器表达式:last(/host/ssl.cert.expiry[{#DOMAIN}]) < 30,级别设为 High。

2. Prometheus Node Exporter Textfile 采集

  • 定时将巡检结果输出为 Prometheus 标准 Metrics 格式并落盘到 /var/lib/node_exporter/textfile_collector/ssl_expiry.promtext # HELP ssl_cert_days_left Days until SSL certificate expires # TYPE ssl_cert_days_left gauge ssl_cert_days_left{domain="api.x7lab.cn"} 78 ssl_cert_days_left{domain="www.baidu.com"} 221

3. Crontab 定时企业微信 / 钉钉机器人 Webhook 告警

  • 配置每日早晨 9:00 执行一次巡检脚本,当 DAYS <= 30 时,调用 Webhook 发送 Markdown 格式警报卡片直达运维群。

六、总结与最佳实践

  1. 务必带上 -servername 参数:确保 SNI 扩展生效,避免命中多虚拟主机中的默认证书;
  2. 连接保护设置 timeout:防止遇到防火墙丢包导致巡检脚本无限期悬挂阻塞;
  3. 并发任务使用 wait 控制:抛弃随意 sleep 的不确定性,确保所有子进程完整收拢后再读盘输出;
  4. 双重防御原则:除外部黑盒探测外,核心入口(如 Nginx / Traefik / ACME 客户端)还应配置白盒自动续签(如 Certbot / Let's Encrypt),做到自动续签为主、远程巡检兜底。
admin

admin

云原生架构 · 全栈开发

感谢阅读本文!记录系统架构思考与实战复盘。专注于 Python、Django、PostgreSQL 与云原生容器化工程实践。