Резолверы уходили в таймаут: DNSSEC сломался после смены ключей
В понедельник утром часть пользователей перестала открывать сайт, а часть — открывала его как обычно. Ни в трафике, ни в логах веб-сервера ничего подозрительного не было: запросы от «сломанных» клиентов до сервера просто не доходили. Разгадка нашлась не в приложении и не в сети, а в DNSSEC — в пятницу вечером кто-то из команды сделал плановую ротацию ключей подписи зоны и не подождал, пока старые данные вымоются из кешей резолверов. Разбираем инцидент по шагам: что видели на мониторинге, какие версии отбросили и что изменили в процессе ротации ключей после этого случая.
Содержание
Что сломалось: первые сигналы
Первым сработал синтетический чек Uptime Kuma, который дергал главную страницу из нескольких регионов. В понедельник около 9 утра по Москве в дашборде появились красные точки — но не по всем локациям сразу, а выборочно: Франкфурт и Амстердам отвечали нормально, а часть проверок из Москвы и один из проверочных узлов в США стабильно уходили в таймаут после 8-10 секунд ожидания.
Почти одновременно в саппорт начали падать тикеты: «сайт не открывается», «висит и потом ошибка тайм-аута». При этом часть клиентов из той же компании писала в общий чат, что у них всё работает. Это первая важная деталь инцидента — проблема была не бинарной (всё упало / всё работает), а зависела от того, у кого какой резолвер и какой у него провайдер.
Дежурный инженер поднял zabbix и grafana — графики нагрузки на сервер, RPS, память, диск — всё было в норме, никаких всплесков CPU, никаких OOM-килов, ничего похожего на разбор из статьи про лимит контейнера, который убивал процесс на пятый день. Сервер жил своей обычной жизнью и не подозревал, что у части интернета его как будто не существует.
Второй важный сигнал: в access-логах nginx не было вообще никаких записей о запросах от «проблемных» пользователей. Ни одного соединения, ни одного TCP SYN в tcpdump на 443-м порту с их IP. Это сразу отсекло гипотезы про приложение, TLS-хендшейк и веб-сервер — раз запрос не долетает до сокета, значит, проблема раньше, на уровне резолвинга имени или маршрутизации.
Первые гипотезы — и почему их отбросили
Дальше команда прошла стандартный чек-лист по убыванию вероятности, и каждую версию закрывали конкретным фактом, а не догадкой.
Гипотеза 1: DDoS или блокировка по IP. Проверили графики трафика на входе — ни всплеска пакетов, ни аномальных SYN-флудов, ни блокировок в fail2ban. В iptables не появилось новых правил, никто руками ничего не трогал. Версию закрыли за десять минут.
Гипотеза 2: проблема на стороне хостинга или сети провайдера. Подняли статус-страницу дата-центра — всё зелёное. Пропинговали сервер с нескольких внешних VPS в разных странах — пинг и трассировка до IP сервера проходили нормально отовсюду, включая машины, с которых сайт был недоступен по имени. То есть до самого IP добраться получалось, а вот превратить домен в этот IP — не всегда.
Гипотеза 3: TTL и обычное DNS-кеширование после недавних изменений записей. Кто-то вспомнил, что неделю назад меняли IP одного из вспомогательных поддоменов, и предположили обычную историю в духе проблем с DNS после переезда — мол, где-то что-то ещё не прогрелось. Но основной A-запись домена никто не трогал уже месяцы, а страдал именно основной домен, а не тот поддомен, который недавно двигали. Гипотезу отбросили после прогона dig +short с нескольких публичных резолверов — свежий и правильный IP отдавали все, включая резолверы, у которых сайт при этом не открывался в браузере. Значит, дело не в устаревшем адресе.
Гипотеза 4: приложение отдаёт 5xx под нагрузкой. Проверили логи приложения и балансировщика — ни одного 500-го, ни одного медленного запроса с проблемных подсетей, потому что запросов оттуда просто не было. Это снова уперлось в тот же факт: проблема на уровне до TCP-соединения.
К этому моменту стало понятно, что разгадка не в сети, не в нагрузке и не в приложении — значит, надо смотреть глубже в саму цепочку резолвинга DNS, а не просто «какой IP отдаётся».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверДиагностика: dig с флагами решил всё
Переломный момент случился, когда дежурный вместо обычного dig +short domain A запустил проверку с явным указанием поведения по DNSSEC:
# обычный запрос с включённой валидацией DNSSEC (поведение по умолчанию)
dig @8.8.8.8 example.com A
# тот же запрос, но с явным отключением проверки подписи (Checking Disabled)
dig +cd @8.8.8.8 example.com A
Первая команда с резолвера Google возвращала SERVFAIL и пустой ответ. Вторая, с флагом +cd (checking disabled), спокойно отдавала корректный IP. Это разница ровно в одном: доверенный резолвер пытается проверить криптографическую подпись ответа и не может — и вместо ответа возвращает ошибку сервера, хотя сами данные на авторитетном сервере абсолютно корректны.
Дальше проверили цепочку доверия целиком:
dig +dnssec example.com DNSKEY
dig +dnssec example.com. DS @<ns-родительской-зоны>
delv example.com A
delv (утилита из состава BIND9, специально созданная для диагностики DNSSEC) прямо в выводе написала resolution failed: insufficient data и указала, что не может выстроить цепочку от DS-записи в родительской зоне до опубликованного набора DNSKEY. То есть проблема была не в самом домене и не в его основной записи, а именно в подписи — цепочка доверия DNSSEC оказалась разорвана.
Стало ясно, почему падали не все: резолверы, которые вообще не проверяют DNSSEC (а таких по-прежнему много, особенно у части провайдеров и в корпоративных сетях с устаревшими настройками), продолжали резолвить домен как ни в чём не бывало. А резолверы с включённой валидацией — крупные публичные вроде Google Public DNS и Cloudflare, а также многие корпоративные и провайдерские — упирались в SERVFAIL и после нескольких попыток и таймаутов возвращали ошибку клиенту.
Реальная причина: ротация ключей без окна на кеши
В пятницу вечером инженер, отвечающий за DNS-инфраструктуру, выполнил плановую ротацию ключей подписи зоны (ZSK — Zone Signing Key) на авторитетном сервере на базе PowerDNS. Ротация ключей DNSSEC — обычная практика безопасности, ничего экзотического: старый ключ имеет свойство «стареть», и его периодически меняют на новый, как ротацию любых других секретов.
Проблема была не в самом факте ротации, а в порядке действий. Правильная последовательность смены ключа в DNSSEC (описанная, в частности, в RFC 7583) требует нескольких шагов с обязательными паузами между ними:
- Опубликовать новый ключ рядом со старым — оба ключа торчат в DNSKEY-наборе зоны одновременно.
- Подождать, пока пройдёт время, равное как минимум TTL DNSKEY-записи плюс запас на кеши резолверов, — чтобы все, кто уже успел закешировать старый набор ключей, гарантированно обновили его и увидели оба ключа.
- Только после этого переключить подпись новых записей на новый ключ и, если менялся KSK (Key Signing Key), обновить DS-запись у регистратора в родительской зоне.
- Подождать ещё раз — уже TTL самой DS-записи у регистратора, — пока резолверы не подтянут актуальную DS.
- И только тогда убрать старый ключ из публикации.
В этом инциденте скрипт ротации, написанный ещё пару лет назад и с тех пор не пересматривавшийся, деактивировал и удалял старый ZSK сразу после публикации нового — без паузы на шаге 2. Часть резолверов, уже закешировавших старый набор DNSKEY, продолжала проверять новые подписи по старому, уже не опубликованному ключу — и закономерно получала несовпадение. С точки зрения такого резолвера зона выглядела как Bogus — подписанной, но с недействительной подписью, что для валидирующего резолвера хуже, чем отсутствие DNSSEC вообще: он не «пожимает плечами» и не пробует без подписи, а жёстко возвращает ошибку.
Отдельно стоит подчеркнуть: если бы зона вообще не использовала DNSSEC, такого отказа просто не произошло бы — сработал бы принцип «нет подписи — нет проверки». Ловушка именно в том, что DNSSEC подписывает ответ криптографически, и любое рассогласование ключей воспринимается резолвером не как «доверимся без проверки», а как признак возможной подмены данных — с точки зрения протокола это неотличимо от MITM-атаки, поэтому реакция обязана быть жёсткой.
Почему пострадали не все и не сразу
Разброс по времени и по резолверам объясняется просто — кешем. У каждого резолвера, который успел получить ответ до ротации, был свой закешированный DNSKEY с собственным TTL. Кто-то из резолверов обновил кеш почти сразу после ротации и словил рассинхронизацию мгновенно. Кто-то держал более старую копию с бóльшим оставшимся TTL и продолжал резолвить нормально ещё некоторое время, пока его собственный кеш не истёк и он не пошёл за свежими данными — и только тогда тоже упирался в разрыв цепочки.
Именно поэтому картина в мониторинге выглядела так рвано: не «всё упало одновременно», а волнообразное появление проблем у всё новых резолверов по мере истечения их индивидуальных TTL. Похожий эффект «времени, растянутого кешами», разбирался в статье про TTL 86400, из-за которого пятиминутный переезд превратился в двухдневный — там TTL мешал распространению нового IP, здесь TTL DNSKEY-записи и DS-записи сыграли похожую роль, только применительно не к адресу, а к ключам подписи.
Важный нюанс: мгновенным откатом ситуацию исправить не получилось. Даже когда старый ключ вернули в публикацию, резолверам с уже закешированной «битой» связкой требовалось дождаться истечения их собственного TTL — сбросить чужой кеш администратор не может. Это стандартное ограничение DNS: вы управляете тем, что публикуете, но не тем, что и как долго держит в памяти чужой резолвер.
Что изменили в процессе после инцидента
Дальнейшие изменения касались не только конкретного бага в скрипте, а всего процесса работы с DNSSEC:
- Переписали скрипт ротации ключей так, чтобы между публикацией нового ключа и удалением старого была обязательная пауза не меньше суммы TTL DNSKEY-записи и разумного запаса, а не мгновенный переход. Для ZSK и KSK сделали разные тайминги, поскольку смена KSK дополнительно тянет за собой обновление DS у регистратора и его собственный TTL.
- Добавили предварительный чек перед любым удалением ключа: скрипт теперь сам прогоняет
dig +dnssecиdelvс нескольких публичных резолверов (включая как минимум один валидирующий и один — для контроля — с отключённой проверкой) и не даёт продолжить ротацию, если цепочка доверия не подтверждается однозначно. - Вынесли ротацию ключей из автоматики в календарь с ручным подтверждением каждого шага. Полностью безучастная автоматизация для операции, где ошибка бьёт по доступности всего домена сразу у части аудитории, оказалась неоправданным риском — решили, что несколько минут внимания инженера на каждом шаге дешевле повторного инцидента.
- Добавили в мониторинг отдельный синтетический чек именно на DNSSEC, а не только на доступность сайта: запрос с
+cdи без него с разных резолверов, с алертом при расхождении статусов. Раньше в мониторинге стояла общая проверка домена и сертификатов, подробно описанная в статье про мониторинг сертификатов и доменов, но она не различала «домен не резолвится» и «домен резолвится, но с ошибкой подписи» — теперь эти два случая различаются явно, и второй триггерит отдельный, более тревожный алерт. - Задокументировали runbook на случай повторного разрыва цепочки доверия: как быстро проверить симптом, как временно откатиться на предыдущий ключ и какие TTL зоны и родительской зоны свести в таблицу перед следующей ротацией.
Отдельно обсуждали радикальный вариант — отказаться от DNSSEC вообще, раз он однажды уже уронил домен для части пользователей. От этой идеи отказались: DNSSEC защищает именно от подмены DNS-ответов и кеш-отравления, и статистика валидирующих резолверов в мире достаточно велика, чтобы не иметь такую защиту стало реальным риском для другой части аудитории. Решили, что источник проблемы был не в самой технологии, а в небрежном процессе её эксплуатации — и чинили процесс, а не выключали защиту.
Как быстро распознать это самому
Если домен то открывается, то нет, причём стабильно у одних и тех же людей и стабильно работает у других, а в логах веб-сервера при этом тишина именно от «сломанных» клиентов — стоит сразу проверить DNSSEC, а не гонять сеть и приложение по кругу. Минимальный набор команд для быстрой диагностики:
# сравнение результата с проверкой подписи и без неё —
# если результат отличается, дело в DNSSEC
dig @8.8.8.8 example.com A
dig +cd @8.8.8.8 example.com A
# подробный разбор цепочки доверия
delv example.com A
# что реально опубликовано на авторитетном сервере прямо сейчас
dig +dnssec example.com DNSKEY @ваш-ns
# что видит родительская зона (через регистратора / TLD-сервер)
dig +dnssec example.com DS
Если dig с +cd работает, а без него — SERVFAIL, вопрос почти наверняка не в сети и не в приложении, а в рассинхронизации ключей или подписей зоны. Дальше смотрят конкретно DNSKEY, DS и время последней ротации — если процесс логирует каждый шаг с таймстампами, найти виновную операцию — вопрос пары минут, а не часов гадания.
Если вы администрируете собственную зону с DNSSEC на выделенном сервере или VPS — стоит держать авторитетный DNS отдельно от продакшен-нагрузки и с запасом ресурсов, чтобы диагностика во время инцидента (лишние dig, delv, перегенерация подписей) не соревновалась за CPU и память с обслуживанием обычного трафика. Подробнее о самой установке разбирали в статье про установку и настройку PowerDNS на VPS, а по частым граблям эксплуатации — в статье PowerDNS на сервере: частые ошибки и решения.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Может ли DNSSEC сломать сайт даже при полностью корректной настройке хостинга и веб-сервера?
Да. DNSSEC работает на уровне DNS, до того как запрос вообще доходит до вашего сервера. Если цепочка подписей нарушена, валидирующий резолвер не пропустит запрос дальше независимо от того, насколько исправно работает сам сервер.
Почему у части пользователей сайт продолжал работать во время такого сбоя?
Потому что не все резолверы проверяют DNSSEC, а те, что проверяют, могли ещё держать в кеше старые, ещё не рассинхронизированные данные — рассинхронизация проявляется постепенно, по мере истечения TTL у разных резолверов.
Как быстро отличить обычный сетевой сбой от проблемы с DNSSEC?
Сравните ответ dig с флагом +cd (проверка подписи отключена) и без него на одном и том же публичном резолвере. Если результаты отличаются — проблема в DNSSEC, а не в сети или сервере.
Нужно ли отключать DNSSEC, чтобы избежать подобных инцидентов в будущем?
Не обязательно и, как правило, не стоит — DNSSEC защищает от подмены DNS-ответов. Правильнее выстроить процесс ротации ключей с соблюдением пауз на TTL и добавить отдельный мониторинг именно цепочки доверия, а не отказываться от защиты целиком.
Кто отвечает за DS-запись, если DNS размещён на своём сервере, а домен куплен у другого регистратора?
DS-запись всегда живёт в родительской зоне и правится через панель регистратора (или через API, если он это поддерживает), даже если авторитетные NS-серверы зоны — ваши собственные. Рассинхронизация между тем, что публикует ваш DNS-сервер, и тем, что видит регистратор в DS, — отдельная частая причина точно таких же сбоев.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →