Мониторинг оплаты доменов в Zabbix через WHOIS

Простое решение для Zabbix, которое автоматически проверяет срок действия доменов через WHOIS, показывает количество дней до окончания оплаты и позволяет заранее получать уведомления о необходимости продления.

В работе любой инфраструктуры есть вещи, которые редко вспоминают каждый день, но их отказ может привести к очень неприятным последствиям. Один из таких примеров — окончание срока действия доменного имени.

Если домен вовремя не продлить, могут перестать открываться сайты, API, личные кабинеты, почта, VPN-сервисы, внешние интеграции и другие важные сервисы. При этом проблема может выглядеть как авария сети, сбой DNS или недоступность сервера, хотя настоящая причина будет банальной — домен не был оплачен.

Чтобы не контролировать такие вещи вручную, можно добавить проверку доменов в Zabbix.


Что делает решение

Данное решение добавляет в Zabbix мониторинг сроков оплаты доменов.

Скрипт:

  • читает список доменов из файла;
  • автоматически добавляет их в Zabbix через Low-Level Discovery;
  • выполняет WHOIS-запрос по каждому домену;
  • ищет поле окончания срока действия домена;
  • возвращает дату окончания оплаты;
  • считает количество дней до окончания;
  • показывает статус проверки;
  • возвращает текст ошибки, если данные получить не удалось.

На основе этих данных в Zabbix можно настроить триггеры, например:

  • домен истекает менее чем через 30 дней;
  • домен истекает менее чем через 14 дней;
  • домен уже просрочен;
  • WHOIS-запрос не выполнился;
  • не удалось найти дату окончания регистрации.

Где это может быть полезно

Такой мониторинг особенно полезен для инфраструктуры, где используется много доменов и поддоменов:

  • сайты компании;
  • API-шлюзы;
  • личные кабинеты;
  • почтовые домены;
  • VPN-доступ;
  • внутренние и внешние порталы;
  • интеграции с клиентами и подрядчиками;
  • сервисы мониторинга;
  • домены для тестовых и продуктовых окружений.

Даже если домены продлеваются автоматически, мониторинг всё равно нужен. Автопродление может не сработать из-за проблем с оплатой, банковской картой, регистратором или изменениями в личном кабинете.


Структура решения

Для работы используется каталог:

/etc/zabbix/scripts/domain-payments/

В нём размещаются:

domain_payment.py
domains.txt

Где:

  • domain_payment.py — основной Python-скрипт проверки доменов;
  • domains.txt — список доменов для мониторинга.

Также в конфигурацию Zabbix Agent добавляются UserParameter.


Установка

Создаём каталог для скрипта:

sudo mkdir -p /etc/zabbix/scripts/domain-payments

Создаём файл со списком доменов:

sudo nano /etc/zabbix/scripts/domain-payments/domains.txt

Пример содержимого:

example.ru
example.com
company.site

Можно добавлять комментарии:

example.ru        # основной сайт
api.example.ru # API

Пустые строки и комментарии будут проигнорированы.


Установка скрипта

Создаём файл:

sudo nano /etc/zabbix/scripts/domain-payments/domain_payment.py

Содержимое скрипта:

#!/usr/bin/env python3
import argparse
import datetime as dt
import json
import re
import socket
import sys
from pathlib import Path

DOMAINS_FILE = Path('/etc/zabbix/scripts/domain-payments/domains.txt')
WHOIS_SERVERS = {
'ru': 'whois.tcinet.ru',
'рф': 'whois.tcinet.ru',
'su': 'whois.tcinet.ru',
}
DEFAULT_WHOIS_SERVER = 'whois.iana.org'
TIMEOUT = 10

DOMAIN_RE = re.compile(r'^[A-Za-z0-9][A-Za-z0-9.-]*[A-Za-z0-9]$')
EXPIRY_RE_LIST = [
re.compile(r'^Registry Expiry Date:\s*(\S+)', re.IGNORECASE | re.MULTILINE),
re.compile(r'^Expiry Date:\s*(\S+)', re.IGNORECASE | re.MULTILINE),
re.compile(r'^Expiration Date:\s*(\S+)', re.IGNORECASE | re.MULTILINE),
re.compile(r'^paid-till:\s*(\S+)', re.IGNORECASE | re.MULTILINE),
]


def load_domains():
domains = []
seen = set()
if not DOMAINS_FILE.exists():
return domains
for raw in DOMAINS_FILE.read_text(encoding='utf-8').splitlines():
line = raw.split('#', 1)[0].strip().lower().rstrip('.')
if not line or line in seen:
continue
if not DOMAIN_RE.match(line):
continue
domains.append(line)
seen.add(line)
return domains


def whois_server(domain):
tld = domain.rsplit('.', 1)[-1].lower()
return WHOIS_SERVERS.get(tld, DEFAULT_WHOIS_SERVER)


def query_whois(domain):
server = whois_server(domain)
with socket.create_connection((server, 43), TIMEOUT) as sock:
sock.settimeout(TIMEOUT)
sock.sendall((domain + '\r\n').encode('utf-8'))
chunks = []
while True:
try:
chunk = sock.recv(4096)
except socket.timeout:
break
if not chunk:
break
chunks.append(chunk)
return b''.join(chunks).decode('utf-8', 'replace')


def parse_datetime(value):
value = value.strip()
candidates = [value]
if value.endswith('Z'):
candidates.append(value[:-1] + '+00:00')
for candidate in candidates:
try:
parsed = dt.datetime.fromisoformat(candidate)
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=dt.timezone.utc)
return parsed.astimezone(dt.timezone.utc)
except ValueError:
pass
for fmt in ('%Y-%m-%d', '%Y.%m.%d', '%d.%m.%Y'):
try:
parsed_date = dt.datetime.strptime(value, fmt).date()
return dt.datetime.combine(parsed_date, dt.time.min, tzinfo=dt.timezone.utc)
except ValueError:
pass
raise ValueError('unsupported expiry date format: ' + value)


def paid_till(domain):
text = query_whois(domain)
for regex in EXPIRY_RE_LIST:
match = regex.search(text)
if match:
expires_at = parse_datetime(match.group(1))
now = dt.datetime.now(dt.timezone.utc)
days = int((expires_at - now).total_seconds() // 86400)
return {
'domain': domain,
'ok': 1,
'paid_till': expires_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
'days': days,
'error': '',
}
raise RuntimeError('expiry/paid-till field not found')


def result(domain):
try:
return paid_till(domain)
except Exception as exc:
return {
'domain': domain,
'ok': 0,
'paid_till': '',
'days': -1,
'error': str(exc),
}


def discovery():
return {'data': [{'{#DOMAIN}': domain} for domain in load_domains()]}


def main():
parser = argparse.ArgumentParser()
parser.add_argument('mode', choices=['discovery', 'days', 'paid_till', 'ok', 'error', 'json'])
parser.add_argument('domain', nargs='?')
args = parser.parse_args()

if args.mode == 'discovery':
print(json.dumps(discovery(), ensure_ascii=False, separators=(',', ':')))
return 0

if not args.domain:
print('domain argument is required', file=sys.stderr)
return 2

domain = args.domain.strip().lower().rstrip('.')
data = result(domain)
if args.mode == 'json':
print(json.dumps(data, ensure_ascii=False, separators=(',', ':')))
elif args.mode == 'days':
print(data['days'])
elif args.mode == 'paid_till':
print(data['paid_till'])
elif args.mode == 'ok':
print(data['ok'])
elif args.mode == 'error':
print(data['error'])
return 0


if __name__ == '__main__':
raise SystemExit(main())

Делаем скрипт исполняемым:

sudo chmod +x /etc/zabbix/scripts/domain-payments/domain_payment.py

Выставляем права:

sudo chown -R zabbix:zabbix /etc/zabbix/scripts/domain-payments

Проверка вручную

Проверяем discovery:

sudo -u zabbix /etc/zabbix/scripts/domain-payments/domain_payment.py discovery

Пример ответа:

{"data":[{"{#DOMAIN}":"example.ru"},{"{#DOMAIN}":"example.com"}]}

Проверяем количество дней до окончания домена:

sudo -u zabbix /etc/zabbix/scripts/domain-payments/domain_payment.py days example.ru

Проверяем дату окончания:

sudo -u zabbix /etc/zabbix/scripts/domain-payments/domain_payment.py paid_till example.ru

Проверяем полный JSON:

sudo -u zabbix /etc/zabbix/scripts/domain-payments/domain_payment.py json example.ru

Пример ответа:

{"domain":"example.ru","ok":1,"paid_till":"2026-08-15T00:00:00Z","days":101,"error":""}

Настройка Zabbix Agent

Создаём отдельный конфигурационный файл:

sudo nano /etc/zabbix/zabbix_agentd.d/domain_payment.conf

Добавляем UserParameter:

UserParameter=domain.payment.discovery,/etc/zabbix/scripts/domain-payments/domain_payment.py discovery
UserParameter=domain.payment.days[*],/etc/zabbix/scripts/domain-payments/domain_payment.py days "$1"
UserParameter=domain.payment.paid_till[*],/etc/zabbix/scripts/domain-payments/domain_payment.py paid_till "$1"
UserParameter=domain.payment.ok[*],/etc/zabbix/scripts/domain-payments/domain_payment.py ok "$1"
UserParameter=domain.payment.error[*],/etc/zabbix/scripts/domain-payments/domain_payment.py error "$1"
UserParameter=domain.payment.json[*],/etc/zabbix/scripts/domain-payments/domain_payment.py json "$1"

Перезапускаем агент:

sudo systemctl restart zabbix-agent

Или для Zabbix Agent 2:

sudo systemctl restart zabbix-agent2

Проверяем, что агент отдаёт данные:

zabbix_get -s 127.0.0.1 -k domain.payment.discovery

Проверка конкретного домена:

zabbix_get -s 127.0.0.1 -k 'domain.payment.days[example.ru]'

Какие ключи используются

КлючНазначение
domain.payment.discoveryАвтоматическое обнаружение доменов из файла domains.txt
domain.payment.days[domain]Количество дней до окончания оплаты домена
domain.payment.paid_till[domain]Дата окончания оплаты домена
domain.payment.ok[domain]Статус проверки: 1 — успешно, 0 — ошибка
domain.payment.error[domain]Текст ошибки при неудачной проверке
domain.payment.json[domain]Полный JSON с результатом проверки

Настройка шаблона в Zabbix

В Zabbix необходимо создать шаблон, например:

Template Domain Payment Monitoring

В шаблоне создаётся LLD-правило:

domain.payment.discovery

Макрос обнаружения:

{#DOMAIN}

Далее создаются item prototypes.

Количество дней до окончания

Name: Domain {#DOMAIN}: days before expiration
Key: domain.payment.days[{#DOMAIN}]
Type: Zabbix agent
Type of information: Numeric (signed)

Дата окончания оплаты

Name: Domain {#DOMAIN}: paid till
Key: domain.payment.paid_till[{#DOMAIN}]
Type: Zabbix agent
Type of information: Text

Статус проверки

Name: Domain {#DOMAIN}: check status
Key: domain.payment.ok[{#DOMAIN}]
Type: Zabbix agent
Type of information: Numeric (unsigned)

Ошибка проверки

Name: Domain {#DOMAIN}: error
Key: domain.payment.error[{#DOMAIN}]
Type: Zabbix agent
Type of information: Text

Примеры триггеров

Домен истекает менее чем через 30 дней

last(/Template Domain Payment Monitoring/domain.payment.days[{#DOMAIN}])<30

Домен истекает менее чем через 14 дней

last(/Template Domain Payment Monitoring/domain.payment.days[{#DOMAIN}])<14

Домен уже просрочен

last(/Template Domain Payment Monitoring/domain.payment.days[{#DOMAIN}])<0

Ошибка проверки WHOIS

last(/Template Domain Payment Monitoring/domain.payment.ok[{#DOMAIN}])=0

Рекомендуемая логика оповещений

Для удобства можно разделить уровни важности:

УсловиеУровень
Меньше 30 днейWarning
Меньше 14 днейAverage
Меньше 7 днейHigh
Домен просроченDisaster
WHOIS не отвечает или дата не найденаAverage

Это позволит заранее получать уведомления и не доводить ситуацию до фактической недоступности сервиса.


Особенности работы

Для доменов .ru, .рф и .su используется WHOIS-сервер:

whois.tcinet.ru

Для остальных доменов используется:

whois.iana.org

Скрипт ищет дату окончания по нескольким возможным полям:

Registry Expiry Date
Expiry Date
Expiration Date
paid-till

Это позволяет использовать его не только для российских доменных зон, но и для части международных доменов.


Возможные проблемы

Не найдено поле окончания срока действия

Ошибка:

expiry/paid-till field not found

Это значит, что WHOIS-ответ по домену не содержит ожидаемого поля с датой окончания.

Причины могут быть разные:

  • регистратор скрывает часть WHOIS-данных;
  • доменная зона использует нестандартный формат ответа;
  • WHOIS-запрос ушёл не на тот сервер;
  • домен не существует;
  • WHOIS-сервис временно недоступен.

Ошибка соединения

Если WHOIS-сервер недоступен, в Zabbix будет возвращён статус:

ok = 0

А в поле error будет записана причина ошибки.

Домен не появляется в Zabbix

Нужно проверить:

cat /etc/zabbix/scripts/domain-payments/domains.txt

Затем выполнить:

sudo -u zabbix /etc/zabbix/scripts/domain-payments/domain_payment.py discovery

Если домена нет в discovery, значит он не проходит проверку регулярным выражением или был некорректно указан в файле.


Зачем это нужно

Контроль оплаты доменов — это простая, но важная часть инфраструктурного мониторинга.

Такой мониторинг позволяет:

  • заранее видеть домены, которые скоро истекают;
  • получать уведомления до наступления проблемы;
  • исключить ручную проверку доменов;
  • снизить риск простоя сайтов и сервисов;
  • быстро понимать, что проблема связана именно с доменом;
  • централизованно контролировать все доменные имена в Zabbix.

В результате Zabbix становится не только системой мониторинга серверов, сетевого оборудования и сервисов, но и инструментом контроля важных инфраструктурных зависимостей.

Действия с записью
Назад в блог
Скачать файл: zbx_export_templates.yaml

Комментарии

Комментариев пока нет.

Войдите, чтобы оставить комментарий.