Domain Registration Expiration Notifications
This is not about the SSL certificate: that one can be reissued in a minute, but the registration of the domain name itself is not so easy to get back. If you forget to renew a domain, it goes into the redemption period, then to auction — and the site, along with its email, ceases to exist. Certbot won’t save you here: the certificate may be fresh while the domain already belongs to someone else.
The catch is that the registration expiry date comes from whois, and every registrar
labels the field differently: Registry Expiry Date, Expiration Date, Expiry date,
paid-till (that’s how .ru/.рф do it via TCINET), and a dozen other variants. Date
formats also differ — from ISO to YYYY.MM.DD. Below is a script that digests all the
common variants, normalizes the date via date -d, and sends a
push when only a few days remain.
Check script
Section titled “Check script”/usr/local/bin/notifly-domain-check:
#!/usr/bin/env bash# Usage:# notifly-domain-check example.com example.ru notifly.рф [...]set -euset -a; source /etc/notifly.env; set +a
WARN_DAYS=30 # priority 5 (normal warning)CRIT_DAYS=7 # priority 9 (loud, heads-up)
# Extracts the raw registration expiry date from whois output.# We try registrar labels top to bottom and take the first match.extract_expiry() { local w="$1" # .ru / .рф / .su via TCINET: paid-till field, format YYYY.MM.DD local ru ru=$(echo "$w" | grep -iE '^\s*paid-till:' | head -n1 | sed -E 's/^[^:]*:\s*//') if [ -n "$ru" ]; then # 2027.04.15 -> 2027-04-15 so date -d parses it reliably echo "${ru//./-}" return fi # All other TLDs: typical labels for the expiry date echo "$w" | grep -iE \ 'Registry Expiry Date|Registrar Registration Expiration Date|Expiration Date|Expiration Time|Expiry Date|Expiry date|expire:|expires:|Domain Expiration Date|renewal date' \ | head -n1 | sed -E 's/^[^:]*:\s*//'}
for DOMAIN in "$@"; do WHOIS=$(whois "$DOMAIN" 2>/dev/null || true)
if [ -z "$WHOIS" ]; then /usr/local/bin/notifly-send \ "🚨 Домен: whois не ответил — $DOMAIN" \ "Не удалось получить данные whois. Проверьте сеть или лимит запросов." 8 continue fi
RAW=$(extract_expiry "$WHOIS")
if [ -z "$RAW" ]; then /usr/local/bin/notifly-send \ "🚨 Домен: не нашёл дату — $DOMAIN" \ "В выводе whois нет знакомого поля с датой. Проверьте вручную: whois $DOMAIN" 8 continue fi
# Normalize into unix time. If the format is unexpected — report, don't stay silent. if ! EXP_TS=$(date -d "$RAW" +%s 2>/dev/null); then /usr/local/bin/notifly-send \ "🚨 Домен: не смог разобрать дату — $DOMAIN" \ "whois вернул: $RAW. Формат не распознан, проверьте вручную." 8 continue fi
NOW_TS=$(date +%s) DAYS_LEFT=$(( (EXP_TS - NOW_TS) / 86400 )) EXP_HUMAN=$(date -d "@$EXP_TS" +%Y-%m-%d)
if [ "$DAYS_LEFT" -le "$CRIT_DAYS" ]; then /usr/local/bin/notifly-send \ "🔥 Домен КРИТИЧНО: $DOMAIN — $DAYS_LEFT дн." \ "Регистрация истекает $EXP_HUMAN. Срочно продлите, иначе потеряете домен!" 9 elif [ "$DAYS_LEFT" -le "$WARN_DAYS" ]; then /usr/local/bin/notifly-send \ "⚠️ Домен: $DOMAIN истекает через $DAYS_LEFT дн." \ "Регистрация домена действует до $EXP_HUMAN. Пора продлевать." 5 fidoneMake it executable:
sudo chmod +x /usr/local/bin/notifly-domain-checkYou need the whois package (usually apt install whois / dnf install whois). It
also understands Cyrillic domains — TCINET correctly returns paid-till for .рф too.
A domain is renewed once a year, so there’s no point checking more than once a day.
Using cron
Section titled “Using cron”Once a day at 9 am:
0 9 * * * root /usr/local/bin/notifly-domain-check \ example.com \ notifly.ru \ notifly.рфDomains are passed as arguments — as many as you like, across different zones.
Using systemd timer (recommended)
Section titled “Using systemd timer (recommended)”/etc/systemd/system/notifly-domain.service:
[Unit]Description=Notifly domain expiry check
[Service]Type=oneshotExecStart=/usr/local/bin/notifly-domain-check example.com notifly.ru notifly.рф/etc/systemd/system/notifly-domain.timer:
[Unit]Description=Run notifly-domain-check daily
[Timer]OnCalendar=*-*-* 09:00:00Persistent=true
[Install]WantedBy=timers.targetPersistent=true runs a missed check if the server was off at 9 am.
Activate it:
sudo systemctl daemon-reloadsudo systemctl enable --now notifly-domain.timersudo systemctl list-timers notifly-domain.timerWindows: PowerShell + Task Scheduler
Section titled “Windows: PowerShell + Task Scheduler”Windows has no built-in whois, so we use whois.exe from Sysinternals
(https://learn.microsoft.com/sysinternals/downloads/whois) — unpack it, for
example, into C:\Tools\whois.exe. On first run it asks you to accept the
license: whois.exe -accepteula example.com.
The script uses the common function Send-Notifly from the
sysadmin/index template.
param( [string[]]$Domains = @("example.com", "notifly.ru"), [string] $WhoisExe = "C:\Tools\whois.exe"). C:\scripts\Notifly.ps1
$WarnDays = 30$CritDays = 7
# Expiry-date field labels used by different registrars$patterns = @( 'Registry Expiry Date', 'Registrar Registration Expiration Date', 'Expiration Date', 'Expiry Date', 'Expiry date', 'Domain Expiration Date', 'expire', 'paid-till')
foreach ($domain in $Domains) { try { $whois = & $WhoisExe -nobanner $domain 2>$null
$raw = $null foreach ($p in $patterns) { $line = $whois | Select-String -Pattern ([regex]::Escape($p)) | Select-Object -First 1 if ($line) { $raw = ($line.Line -split ':', 2)[1].Trim() # .ru/.рф paid-till: YYYY.MM.DD -> YYYY-MM-DD if ($p -eq 'paid-till') { $raw = $raw -replace '\.', '-' } break } }
if (-not $raw) { Send-Notifly -Title "🚨 Домен: не нашёл дату — $domain" ` -Message "В выводе whois нет знакомого поля. Проверьте вручную." -Priority 8 continue }
$exp = [datetime]::Parse($raw, [System.Globalization.CultureInfo]::InvariantCulture) $daysLeft = [int]($exp - (Get-Date)).TotalDays $expHuman = $exp.ToString('yyyy-MM-dd')
if ($daysLeft -le $CritDays) { Send-Notifly -Title "🔥 Домен КРИТИЧНО: $domain — $daysLeft дн." ` -Message "Регистрация истекает $expHuman. Срочно продлите!" -Priority 9 } elseif ($daysLeft -le $WarnDays) { Send-Notifly -Title "⚠️ Домен: $domain истекает через $daysLeft дн." ` -Message "Регистрация действует до $expHuman. Пора продлевать." -Priority 5 } } catch { Send-Notifly -Title "🚨 Домен: ошибка проверки $domain" ` -Message "$_" -Priority 8 }}If you can’t install a third-party whois.exe, you can look up the expiry date via the
registry’s RDAP service (Invoke-RestMethod "https://rdap.org/domain/$domain"), but the
completeness of the data depends on the zone — for .ru, RDAP often returns less than
whois.
Schedule — once a day:
$Action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Domain-Check.ps1"$Trigger = New-ScheduledTaskTrigger -Daily -At 09:00$Princ = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel HighestRegister-ScheduledTask -TaskName "Notifly Domain Check" ` -Action $Action -Trigger $Trigger -Principal $Princ -Description "Notifly: проверка срока регистрации доменов"Benefits
Section titled “Benefits”- The domain won’t “drop” out of nowhere. A calm warning arrives a month ahead, a loud one a week ahead: forgetting is no longer possible.
- Works with any zone, including
.ruand.рф(thepaid-tillfield from TCINET) — one script for your whole fleet of domains, with zones passed as arguments. - A separate safeguard against that exact mistake, where SSL renews automatically but everyone forgot to pay for the domain itself because “the site works, doesn’t it?”.
What to improve next
Section titled “What to improve next”- Include the registrar name and status (
clientTransferProhibited, etc.) in the message via theextrasfield — so you immediately see where to go to renew. - Store an “already warned” state, like in the disk recipe, so you don’t send the same notification every day.
- Also check the delegation term at the NS-server registrar — sometimes it’s that which “expires”, not the domain itself.