Skip to the content.

DNS у Beget: намерение и факт — разные вещи

Четыре грабли, найденные 13.09.2026 на живой задаче (перевод поддомена с shared-хостинга на VPS). Общее у них одно: панель и dns/getData показывают, что собирались сделать, а не то, что отдаётся миру.

Поддомена нет — это лечится domain_add_subdomain, а не записью зоны

У Beget поддомен — отдельная сущность, а не запись в зоне родителя. Пока её нет, dns/getData по FQDN отвечает METHOD_FAILED, и любая запись в это имя обязана остановиться: read-merge-write без прочитанной зоны превратился бы в changeRecords по пустому набору, то есть в стирание.

Прежний текст ошибки (NO_ZONE_DATA) советовал единственный обход — dns_set_records с replace_all=True. Для самой частой причины совет неверен и опасен: сущность так не создаётся, а слепая запись зоны — ровно то, от чего защита и стоит.

Теперь _get_result() различает два случая:

Детект работает на всех путях записи: dns_set_* и dns_patch_record ходят через один _get_result().

Создание поддомена НЕ создаёт пустую сущность

domain/addSubdomainVirtual возвращает только id, и по ответу кажется, что записей нет. На деле Beget тут же провижинит свой набор:

A   -> 87.236.16.28            (shared-хостинг Beget)
TXT -> v=spf1 redirect=beget.com
MX  -> mx1.beget.com (10), mx2.beget.com (20)

Два следствия:

  1. имя начинает резолвиться на shared-хостинг до вашей dns_set_a. На домене, который уже кому-то отдан, это видимая подмена, а не пауза;
  2. MX и SPF переживают dns_set_a (тот мерджит зону, а не заменяет) и живут дальше молча — для поддомена под API это мусор, а для почты работающий приём писем на адрес, о котором владелец не знает.

domain_add_subdomain после создания читает зону и отдаёт фактические записи в поле records, плюс note с этим предупреждением. Провал чтения не делает вызов неуспешным: поддомен уже создан, причина уходит в records_error.

Catch-all зоны невидим для dns/getData

dns_get('itrobot.ru') отдаёт A -> 185.137.235.2 и никакого wildcard. При этом любое несуществующее имя в зоне отвечает, и не адресом корня:

nslookup zzz-not-exist-test.itrobot.ru  ->  87.236.16.28
nslookup itrobot.ru                     ->  185.137.235.2

Прочитав зону, агент делает вывод «записи нет, имя свободно». Вывод неверен: имя отвечает чужим сервером. Цена ошибки конкретна — запуск certbot на имя, которое на самом деле смотрит на stage-хостинг: webroot-проверка уходит не на тот сервер, попытка сгорает и приближает лимит Let’s Encrypt на домен.

Поэтому dns_get рядом с записями зоны отдаёт поле effective — фактический ответ авторитативного NS по запрошенному FQDN, спрошенный напрямую по UDP (src/mcp_beget/dnsprobe.py). Отключается параметром check_effective=False.

success от dns_set_* означает «принято панелью»

dns_set_a вернул success, dns_get подтвердил новое значение — и это ничего не говорит о мире: dns_get читает тот же источник, куда писал. Авторитативный NS в тот же момент отдавал старое значение, совпало через несколько минут.

Вручную сверять надо nslookup, а НЕ Resolve-DnsName -Server. Флаг -Server не обходит клиентский кеш Windows: в том же сеансе Resolve-DnsName -Server ns1.beget.com отдавал старый адрес одновременно с тем, как nslookup на тот же сервер отдавал новый. Правка выглядела непримененной уже после того, как применилась.