لو شهادة SSL خلصت يوم جمعة الساعة 3 الفجر، موقعك بيرجع للزوار برسالة NET::ERR_CERT_DATE_INVALID لحد الاتنين الصبح — و Google مش هيستنى، و SEO بيتأثر في 24 ساعة. الـ workflow اللي هنا بيمسك المشكلة دي قبل ما تحصل، بتكلفة صفر دولار.
المشكلة باختصار
كتير من الفرق بتعتمد على إن Let's Encrypt أو cert-manager "هيعمل renew لوحده". ده صحيح 95% من الوقت. المشكلة في الـ 5% الباقيين: DNS اتغير، rate limit من Let's Encrypt، webhook لـ cert-manager وقع، الشهادة اتجدّدت بس nginx مـعـملش reload. في حادثة Microsoft Teams يوم 3 فبراير 2020 شهادة انتهت وأوقفت الخدمة ساعات — وده فريق معاه موارد ضخمة. ركز: الأتمتة لوحدها مش كفاية، لازم verifier تاني بيتأكد إن الأتمتة شغالة فعلاً.
ليه auto-renew بتاع Let's Encrypt مش كفاية لوحده
Let's Encrypt بيبعت إيميل قبل انتهاء الشهادة بـ 20 و 10 و 1 يوم — بس على الإيميل المسجّل وقت إصدار الشهادة. لو المطوّر ده ساب الشركة من سنتين، الإيميل بيتوه. كمان ACME v2 بيفرض rate limit قدره 50 شهادة/أسبوع لكل registered domain، فلو الـ renew فشل مرتين بسبب DNS propagation، ممكن توصل للـ limit وتفضل أسبوع كامل بدون شهادة.
والأهم: بدءًا من 2026 الصناعة بتتجه لشهادات عمرها 47 يوم بدل 398 يوم. يعني كل شهر تقريبًا في renew. لو واحد منهم فشل ومفيش monitor خارجي، الموقع بيطير.
الحل: GitHub Actions يفحص كل 12 ساعة ويبعت Slack
المبدأ بسيط: GitHub Actions بيشغّل openssl s_client على كل دومين، يحسب كام يوم فاضل، ويبعت Slack webhook على ثلاث مراحل — 30 يوم، 15 يوم، 7 أيام (قاعدة 30-15-7 المتعارف عليها في الصناعة).
ليه GitHub Actions مش سيرفرك؟ لأن الـ monitor المفروض يكون خارج البنية اللي بيراقبها. لو السيرفر وقع، cron المحلي مش هيبعت تنبيه. GitHub Actions ده SLA تاني مختلف تمامًا.
الخطوات
- اعمل repo جديد (خاص أو عام مش فارق) اسمه
ssl-monitor. - ضيف Slack Incoming Webhook في Settings → Secrets → Actions باسم
SLACK_WEBHOOK. - احفظ الملف اللي تحت في
.github/workflows/ssl-check.yml. - حدّث مصفوفة
DOMAINSبدومينات موقعك. - شغّل
workflow_dispatchيدويًا مرة علشان تتأكد إن الـ Slack بيوصل.
الـ workflow كامل (انسخه وشغّله)
name: SSL & Domain Expiry Monitor
on:
schedule:
- cron: '0 */12 * * *' # كل 12 ساعة
workflow_dispatch:
jobs:
check-ssl:
runs-on: ubuntu-latest
steps:
- name: Install whois
run: sudo apt-get update && sudo apt-get install -y whois
- name: Check SSL and domain expiry
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: |
DOMAINS=("ahmedhaies.com" "lms.ahmedhaies.com" "api.ahmedhaies.com")
THRESHOLDS=(30 15 7)
notify() {
local msg="$1"
curl -sS -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"text\": \"$msg\"}"
}
for d in "${DOMAINS[@]}"; do
# 1) SSL expiry
EXP=$(echo | openssl s_client -servername "$d" -connect "$d:443" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
EXP_TS=$(date -d "$EXP" +%s)
NOW_TS=$(date +%s)
DAYS=$(( (EXP_TS - NOW_TS) / 86400 ))
echo "SSL $d -> $DAYS days"
for t in "${THRESHOLDS[@]}"; do
if [ "$DAYS" -eq "$t" ]; then
notify ":lock: SSL for *$d* expires in *$DAYS days* ($EXP)"
fi
done
# 2) Domain (whois) expiry
WEXP=$(whois "$d" | grep -iE 'Registry Expiry Date|Expiration Date' \
| head -1 | awk -F': ' '{print $2}')
if [ -n "$WEXP" ]; then
WEXP_TS=$(date -d "$WEXP" +%s)
WDAYS=$(( (WEXP_TS - NOW_TS) / 86400 ))
echo "Domain $d -> $WDAYS days"
if [ "$WDAYS" -lt 60 ]; then
notify ":earth_africa: Domain *$d* expires in *$WDAYS days*"
fi
fi
done