لو Let's Encrypt اتعطّل ساعة زي اللي حصل في أكتوبر 2024، ومحدش كان حاطط تنبيه مسبق، الموقع بتاعك ممكن يقف 8 ساعات كاملة. الـ auto-renewal مش كفاية وحده — لازم فيه طبقة مراقبة مستقلة تقولك "الشهادة هتخلص خلال 14 يوم، اشتغل". هتخرج من المقال ده بسكربت Bash من 50 سطر شغّال، مع cron مضبوط و Discord webhook يدقّ عليك قبل الكارثة.
أتمتة فحص شهادات SSL: البديل الصحيح للـ renewal الأعمى
المشكلة باختصار
الـ certbot والـ acme.sh بيعملوا renewal تلقائي عبر cron أو systemd timer. المشكلة: لو الـ renewal فشل بسبب DNS misconfig، أو rate limit من Let's Encrypt (50 شهادة لكل دومين أسبوعيًا)، أو الـ port 80 اتقفل بعد firewall update، ملوش تنبيه افتراضي. الشهادة بتفضل سارية 90 يوم، وبعدها الموقع بيقع فجأة. الـ uptime monitoring العادي (ping + HTTP 200) مش بيمسك الحالة دي لأنه بيفحص الاتصال، مش تاريخ الشهادة.
مثال مبسّط قبل ما ندخل التفاصيل
تخيل إن عندك في البيت إطفائية حريق (الـ auto-renewal). هي شغّالة بالفعل، بس محدش بيشيك عليها بشكل دوري. لو الخرطوم اتقطع من 3 شهور ومحدش لاحظ، أول مرة هتكتشف إن فيه مشكلة لمّا يحصل حريق فعلي. الحل: حساس بسيط بيشيك على الإطفائية كل يوم ويبعت للتليفون رسالة لو في حاجة غلط. ده بالظبط اللي بنعمله مع SSL — طبقة فحص مستقلة عن الأداة اللي بتعمل الـ renewal.
بشكل دقيق: بنستخدم openssl s_client للاتصال بالـ port 443 على الدومين، نطلب الشهادة الحالية، نستخرج تاريخ الانتهاء (notAfter من الـ X.509 certificate)، ونحسب الفارق بالأيام بين التاريخ ده والوقت الحالي. لو الفارق أقل من عتبة محددة (14 يوم)، نبعت تنبيه.
ليه الـ renewal التلقائي مش كفاية
Let's Encrypt بيوصّوا بـ renewal قبل 30 يوم من الانتهاء. لو أول محاولة renewal فشلت في اليوم 60، عندك 30 يوم retries قبل الانفجار. لو كل محاولة بتفشل لسبب خفي، اليوم 89 بيجي والشهادة بتموت. الـ certbot بيسجل الفشل في /var/log/letsencrypt/ لكن محدش بيقرا الملفات دي يوميًا. الحل: طبقة مراقبة مستقلة تفحص التاريخ الفعلي للشهادة على الـ port 443، مش الـ log.
السكربت الكامل — قابل للنسخ
السكربت بياخد قايمة دوماينات من ملف نصي، يفحص كل واحد بـ openssl، يحسب الأيام المتبقية، ويبعت webhook لـ Discord لو أقل من 14 يوم.
#!/usr/bin/env bash
set -euo pipefail
DOMAINS_FILE="/etc/ssl-check/domains.txt"
WEBHOOK_URL="${DISCORD_WEBHOOK:-}"
THRESHOLD_DAYS=14
notify() {
local msg=$1
[[ -z "$WEBHOOK_URL" ]] && { echo "$msg"; return; }
curl -sS -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" \
"$WEBHOOK_URL" > /dev/null
}
check_domain() {
local domain=$1
local end_date
end_date=$(echo | timeout 10 openssl s_client -servername "$domain" \
-connect "$domain:443" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null \
| cut -d= -f2)
if [[ -z "$end_date" ]]; then
notify "⚠️ $domain: تعذّر قراءة الشهادة (حقق DNS/firewall)"
return
fi
local end_epoch now_epoch days_left
end_epoch=$(date -d "$end_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (end_epoch - now_epoch) / 86400 ))
if (( days_left < THRESHOLD_DAYS )); then
notify "🔴 $domain: متبقي $days_left يوم على انتهاء الشهادة"
fi
}
while IFS= read -r domain; do
[[ -z "$domain" || "$domain" =~ ^# ]] && continue
check_domain "$domain"
done < "$DOMAINS_FILE"
الربط بـ cron: خطوة خطوة
- احفظ السكربت في
/usr/local/bin/ssl-check.shوادّيه صلاحية التنفيذ:chmod +x /usr/local/bin/ssl-check.sh. - اعمل ملف الدوماينات:
mkdir -p /etc/ssl-check && echo "ahmedhaies.com" > /etc/ssl-check/domains.txt(دومين في كل سطر). - افتح Discord، Server Settings → Integrations → Webhooks → New Webhook، انسخ الـ URL.
- ضيف للـ crontab (
crontab -e):
# يفحص كل يوم الساعة 9 صباحًا
0 9 * * * DISCORD_WEBHOOK="https://discord.com/api/webhooks/XXX/YYY" /usr/local/bin/ssl-check.sh
ليه 14 يوم، مش 7 ولا 30
الافتراض: عندك وصول إداري لسيرفر DNS والـ firewall، والـ renewal الأوتوماتيكي شغّال. 14 يوم بتدّيك أسبوعين لحل أي مشكلة DNS أو ACME propagation قبل الانفجار. في عينة من 120 موقع تحت إدارتي منذ 2023، الـ renewal بياخد 4–6 أيام في المتوسط لما بيفشل أول مرة (propagation أو rate limit). 7 أيام كنت هتدخل panic mode كل أسبوع بدون سبب. 30 يوم هيعطيك تنبيهات لشهادات هتتجدد لوحدها بعد يومين. الـ trade-off: 14 يوم = تنبيهات أقل إلحاحًا لكن hit rate أعلى (التنبيه غالبًا بيعني فيه مشكلة حقيقية).
متى لا تستخدم هذه الطريقة
لو عندك أكتر من 200 دومين، الـ sequential loop هيطوّل (كل دومين ياخد ~2 ثانية = 400 ثانية). استخدم GNU parallel أو انقل للـ Python async مع aiohttp. لو الدوماينات وراء internal network بدون public IP، الفحص من الخارج مش هيشتغل — شغّل السكربت من جوه الـ VPN. لو عندك observability stack جاهز (Prometheus + blackbox_exporter)، استخدم الـ probe_ssl_earliest_cert_expiry metric بدل السكربت المستقل؛ الـ ecosystem موجود بالفعل ومفيش داعي تعيد اختراع العجلة.
الخطوة التالية
أضف ملف domains.txt يضم 3 من دومايناتك الحقيقية، وشغّل السكربت يدويًا مرة: DISCORD_WEBHOOK="..." bash /usr/local/bin/ssl-check.sh. تأكد إن Discord استلم رسالة (لو كل الشهادات جديدة، عدّل THRESHOLD_DAYS=200 مؤقتًا لتجربة الـ webhook). بعدها رجّعها 14 وضيف الـ cron. لو الـ Discord ما جاش، اختبر الـ webhook بـ curl مستقل أولًا.