VPN·HUB·CRACK Блокировка DNS - решение · VPN HUB CRACK
Все статьи
Remnawave PRO Remnawave

Блокировка DNS - решение

Обновлено 30 августа 2026 Проверено

Блокировка DNS - решение

26 августа 2026 года у части российских операторов перестал работать обычный DNS. Не «сайт заблокировали», не «нода в реестре» — сломался сам резолвинг. Для владельца панели это худший вид аварии: сервер жив, нода отвечает, подписка отдаётся, а клиент пишет «не работает ютуб» и уходит.

Разберём, что именно произошло, почему вы, скорее всего, не видите этого со своего сервера, и как перестроить конфиги так, чтобы следующая волна прошла мимо.

Что произошло

Вечером 26 августа ТСПУ начали перехватывать открытые DNS-запросы к публичным резолверам. Механика — DNAT: запрос по UDP на порт 53 к 8.8.8.8 или 1.1.1.1 уходит не в Google и не в Cloudflare, а на серверы НСДИ. Для заблокированных доменов оттуда возвращается NXDOMAIN.

Вскрыли это TTL-трюком: пакет с TTL=2 возвращает ICMP Time Exceeded с адреса 195.208.5.1 — это НСДИ, а не Google. Ключевая деталь: TCP на тот же порт 53 к тем же адресам проходит и отдаёт настоящие ответы. Перехватывают именно UDP.

За день до этого, 25–26 августа, посыпались защищённые протоколы. У Ростелекома, Дом.ру, Таттелекома и питерского SkyNet:

  • DoT (853) — TCP-соединение устанавливается, но рвётся с ECONNRESET до завершения TLS-рукопожатия;
  • DoH (443) — соединение открывается, ClientHello уходит, ответа нет.

Обратите внимание на характер: порт не закрыт, соединение не отвергнуто на уровне TCP. Отсекается именно TLS-рукопожатие — то есть работает сигнатурный фильтр, а не блок-лист портов. Практический вывод из этого простой и важный: смена провайдера DNS помогает лучше, чем смена протокола. Фильтр знает dns.google и cloudflare-dns.com, а не «DoH вообще».

Хроника при этом длиннее, чем кажется по новостям. Первые жалобы на DoH у Google и DoT у Cloudflare пошли 21 августа в сети «Билайна» — за четыре дня до волны публикаций. К 25–26 августа к списку добавились МТС и МГТС, а у трёх мелких провайдеров (Kompanon, NECSTEL, TeleMaks) DoH перестал работать полностью. Отдельно стоит знать, что это не первая попытка: по данным иностранного мониторинга, РКН тестировал блокировку DoH и DoT ещё в марте 2026 года, на той же сети «Билайна». То есть перед нами не разовый сбой, а раскатка.

Чего мы не знаем

Здесь придётся быть честным, потому что от этого зависят ваши решения.

Официальных заявлений РКН нет ни по одному эпизоду. История с DNAT на НСДИ пока держится на одном первоисточнике — эксперименте одного исследователя. Замер «ЗаТелекома» от 22–23 августа (3186 проверок, 66 автономных систем, 29 DoH-серверов) дал 73% успеха и вывод авторов: признаков блокировки DoH как протокола не обнаружено. Независимый разбор «Теплицы» зафиксировал подмену DNS-ответов, но с формулировкой «мы пока не знаем» — сертификаты подлинные, классического MITM нет, зависимость от маршрута есть.

То есть: явление реально, но это не тотальный бан по всей стране, а точечная и плавающая фильтрация. Что подводит нас к самому неприятному.

Почему вы этого не видите

Мы прогнали замеры с трёх точек 28 августа. Проверяли сравнение UDP и TCP, подмену ответов, доступность DoH и DoT.

Точка UDP против TCP rutracker.org DoH DoT / TCP:53
РФ, мобильный оператор NOERROR = NOERROR Cloudflare, верно жив всё открыто
РФ, хостинг (дата-центр) NOERROR = NOERROR Cloudflare, верно жив всё открыто
Германия (контроль) NOERROR = NOERROR Cloudflare, верно жив всё открыто

Дополнительно прогнали самую строгую проверку из существующих — запрос к 192.0.2.1. Это TEST-NET-1 из RFC 5737, документационный адрес, DNS-сервера там нет нигде в мире. Если оттуда приходит ответ — значит на пути стоит перехватчик, который отвечает за любой IP. На всех трёх точках — тишина.

замер с абонентской сети РФ — перехвата нет
~ $ dig +short youtube.com @8.8.8.8          # plaintext UDP
108.177.14.136
108.177.14.190
~ $ dig +short +tcp youtube.com @8.8.8.8     # то же по TCP
108.177.14.136
108.177.14.190
──────────────────────────────────────────────────────────────
~ $ dig +time=4 +tries=1 rutracker.org @192.0.2.1
;; no servers could be reached
   192.0.2.1 — TEST-NET-1 (RFC 5737), DNS там нет нигде в мире.
   Таймаут — правильный ответ. Пришёл бы настоящий — это перехват.

Перехвата нет ни на одной нашей точке. И это не опровержение волны, это её главное свойство.

Волна операторо-зависима. Она бьёт по абонентским сетям конкретных провайдеров, а не по дата-центрам. Владелец панели, который проверяет DNS со своего сервера, никогда не увидит проблему абонента Ростелекома. Ваш сервер стоит в ДЦ, у ДЦ другой аплинк и другой профиль фильтрации.

Отсюда весь ад в поддержке: у вас всё зелёное, у клиента ничего не работает, и оба правы. Прежде чем чинить конфиги, зафиксируйте это как рабочую гипотезу — и требуйте замер с машины клиента, а не со своей.

Три разные поломки, которые выглядят одинаково

Клиент пишет одно и то же: «не работает». За этим стоят три разных сбоя на трёх разных стадиях. Лечатся они по-разному, и путать их — значит чинить не то.

Стадия 1. Резолв адреса сервера — до туннеля. В хосте панели в поле «Адрес» стоит домен. Клиент обязан превратить его в IP, чтобы вообще открыть соединение. Делает он это системным резолвером устройства, до всякого шифрования, вне туннеля. Отравлен системный резолвер — VPN не подключается вовсе. Симптом: «подключение не устанавливается», в клиенте ошибка коннекта.

Стадия 2. Резолв сайтов — внутри туннеля. Туннель уже поднят. Клиент запрашивает youtube.com. Если этот запрос уходит открытым UDP мимо туннеля или через зарезанный DoH — сайт не открывается при формально рабочем VPN. Симптом: «VPN подключён, но ютуб не грузится». Самый массовый и самый непонятный для клиента случай.

Стадия 3. Утечка DNS мимо туннеля. Частный случай второй стадии, но с другими последствиями: запросы уходят в открытую, провайдер видит весь список посещаемых доменов, а ответы приходят подменённые. Формально «работает», фактически — и приватности нет, и часть сайтов открывается не туда.

Эти три стадии независимы. Починка одной не чинит остальные — и это ровно та ошибка, на которой строятся народные рецепты последних дней.

Когда клиент вообще делает DNS-запрос

Это главное открытие всего разбора, и оно противоречит интуиции.

Мы собрали стенд: Xray 26.3.27, сервер и клиент на одной машине, VLESS поверх TCP. У сервера свой резолвер 9.9.9.9, у клиента 1.1.1.1 — так по логам сразу видно, кто именно резолвил. Дальше гоняли один и тот же запрос через разные конфигурации и читали логи обеих сторон.

Первый же результат оказался неожиданным. Конфиг с правилом «DNS отправлять напрямую», доменные правила маршрутизации, никаких CIDR-списков. Клиент вообще не обратился к DNS ни разу. В логе сервера:

from 127.0.0.1:62101 accepted tcp:rutracker.org:443 [in >> direct]

Серверу ушёл домен, а не адрес. Резолвил сервер, на своей стороне, внутри туннеля. Клиенту DNS не понадобился вовсе.

Причина в том, как устроен Xray: ядро дёргает DNS-модуль только тогда, когда маршрутизации нужен IP-адрес. Нужен он в двух случаях — если в правилах есть условия по IP или подсетям (ip, CIDR-списки, и routing.domainStrategy выставлен в IPIfNonMatch), либо если на исходящем соединении задан domainStrategy, отличный от AsIs. Если правила чисто доменные, домен едет на сервер как есть, и никакого DNS на устройстве клиента не происходит.

Отсюда вывод, который стоит перечитать дважды: российский сплит-туннель по CIDR-спискам сам создаёт зависимость от DNS, которой без него не было бы. Чем аккуратнее вы разделили «российское напрямую, остальное через ноду» по подсетям, тем сильнее ваши клиенты зависят от резолвера, который сейчас перехватывают.

Именно поэтому сломалось не у всех и не везде. У кого простой конфиг «всё в туннель» — тот волну не заметил. У кого выверенный сплит с сотнями CIDR — тот получил шквал обращений.

Что мы проверили на трафике

Дальше мы прогнали четыре конфигурации и смотрели, куда физически уходит DNS-запрос. Не по документации — по логам.

Конфигурация Что в логе клиента Что увидел сервер Итог
Доменные правила, без CIDR DNS-модуль не вызывался accepted tcp:rutracker.org:443 Резолвит сервер, запросов нет
Сплит по CIDR, обычная секция dns dialing to tcp:1.1.1.1:53 ничего Утечка: DNS ушёл открыто
dns.tag + правило в туннель tunneling request to tcp:1.1.1.1:53 accepted tcp:1.1.1.1:53 DNS прошёл внутри туннеля
fakedns + sniffing внешних запросов: 0 accepted tcp:rutracker.org:443 Внешнего DNS нет вообще

Разница между второй и третьей строкой — одно слово в логе. dialing означает, что запрос ушёл наружу открытым текстом, где его и перехватывают. tunneling request — что он поехал внутрь шифрованного туннеля. Для ТСПУ во втором случае нет никакого DNS-запроса, есть просто поток к вашей ноде.

стенд Xray: куда физически уходит DNS-запрос
### вариант A — сплит по CIDR, обычная секция dns
app/dns: domain rutracker.org will use DNS in order: [UDP:1.1.1.1:53]
app/dns: UDP:1.1.1.1:53 querying DNS for: rutracker.org.
transport/internet: dialing to tcp:1.1.1.1:53            ← НАРУЖУ, открытым текстом
### вариант B — dns.tag + правило inboundTag → proxy
app/dns: domain rutracker.org will use DNS in order: [UDP:1.1.1.1:53]
app/dns: UDP:1.1.1.1:53 querying DNS for: rutracker.org.
proxy/vless/outbound: tunneling request to tcp:1.1.1.1:53  ← ВНУТРЬ туннеля
   Один и тот же запрос. Разница в конфиге — одно поле и одно правило.

Подтверждение с обратной стороны: сервер зафиксировал accepted tcp:1.1.1.1:53 — то есть DNS-запрос действительно доехал до него по туннелю и был выполнен уже оттуда.

подтверждение со стороны сервера — access.log
from 127.0.0.1:62101 accepted tcp:rutracker.org:443  [in >> direct]
from 127.0.0.1:62149 accepted tcp:1.1.1.1:53          [in >> direct]
from DNS accepted udp:9.9.9.9:53  [xray.system.ce8f31e5 >> direct]
   Вторая строка — DNS-запрос клиента, доехавший по туннелю.
   Первая — домен, который клиент отдал серверу, не резолвя сам.
   Третья — собственный резолвер сервера, выполняющий эту работу.

Разбор народного рецепта

Последние дни в чатах владельцев ходит рецепт из двух пунктов: поставить в хостах панели IP вместо домена и включить в HAPP тумблер «Использовать DNS из JSON». Часто с припиской, что одно без другого не работает.

Рецепт рабочий, но объяснение к нему неверное, а из неверного объяснения растут неверные решения.

Про IP вместо домена. Действие осмысленное: поле «Адрес» уходит в ссылку vless://uuid@АДРЕС:port и в поле address исходящего соединения, и клиент резолвит его системным резолвером до рукопожатия. Поставили IP — убрали DNS с критического пути подключения. Но лечит это только первую стадию — «VPN не подключается». К незагружающемуся ютубу внутри работающего туннеля отношения не имеет никакого.

Про тумблер в HAPP. Вот это как раз бьёт во вторую стадию, и это самая ценная часть рецепта. При выключенном тумблере HAPP игнорирует секцию dns из вашей подписки и резолвит собственным резолвером — по умолчанию через DoH Cloudflare. Тот самый, который у ряда операторов сейчас не отвечает. Включение тумблера возвращает управление вашему конфигу.

Но у тумблера есть два невидимых условия. Он работает, только если подписка отдаётся полным конфигом XRAY_JSON — на обычных ссылках vless:// он не сделает ничего. И только если у пользователя не активен собственный профиль маршрутизации Happ. Если вы раздаёте ссылки, а не JSON, клиент включит тумблер, ничего не изменится, и вы оба будете считать, что рецепт не помог.

Про «одно без другого не работает» — это миф. Стадии независимы, в документации HAPP это разные параметры: за адрес сервера отвечает отдельная настройка предрезолва с выбором адреса по пингу, за DNS внутри туннеля — тумблер. Работает и по отдельности. Ощущение связки возникает у того, у кого сломались обе стадии сразу.

Почему «ставьте везде IP» — плохое постоянное правило

Как разовое лечение — нормально. Как политика — вредно, и вот почему.

Официальная документация Remnawave рекомендует именно домен, и по хорошей причине: адреса меняются. Когда IP уедет — а он уедет, — с доменом это правка одной A-записи, а с голым IP это правка всех хостов плюс обновление подписки у каждого клиента. Часть приложений подписку не обновляет вовсе, и такие клиенты просто останутся с мёртвым конфигом.

Добавьте к этому, что блокировки РКН идут по IP и целыми подсетями. Мы это видели своими глазами: 20 августа целиком ушёл диапазон нод одного проекта, до этого — подсеть другого. С доменом переезд занимает минуту, с голым IP превращается в спецоперацию.

И последнее: спрятать адрес через это поле всё равно не выйдет. Домен SNI летит в открытом ClientHello независимо от того, что вы написали в «Адресе».

Что делать: решения по надёжности

1. Загнать DNS внутрь туннеля

Самое устойчивое решение. ТСПУ видит шифрованный поток к вашей ноде, а не DNS-запросы. Проверено на стенде — работает именно так, как заявлено.

В Xray это делается через tag у секции dns и правило маршрутизации, которое ловит помеченный трафик:

{
  "dns": {
    "tag": "dns_q",
    "servers": ["tcp://1.1.1.1:53", "https://dns.google/dns-query"],
    "queryStrategy": "UseIPv4"
  },
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      { "type": "field", "inboundTag": ["dns_q"], "outboundTag": "proxy" }
    ]
  }
}

Правило с inboundTag должно стоять первым — иначе его перехватит какое-нибудь правило выше и DNS уедет напрямую.

Здесь есть ловушка, из-за которой приём часто «не работает». В Xray у DNS-серверов бывает суффикс +local — например https+local:// или tcp+local://. Такие серверы по определению идут мимо маршрутизации, напрямую через локальное соединение. Пометить их тегом и завернуть в туннель невозможно. Если у вас в шаблоне стоит https+local://, приём не заработает, сколько правил ни пиши.

Для sing-box то же самое делается полем detour у DNS-сервера, для mihomo — включением respect-rules (по умолчанию оно выключено, и это отдельная причина утечек) вместе с proxy-server-nameserver.

2. Убрать внешний DNS совсем: fakedns и sniffing

Более радикальный вариант: клиент вообще не делает DNS-запросов. На запрос приложения ядро мгновенно отдаёт адрес из служебного пула, а настоящий домен восстанавливает из TLS-рукопожатия и передаёт на сервер. На стенде — ноль внешних DNS-запросов, сервер получает домен.

{
  "fakedns": [{ "ipPool": "198.18.0.0/16", "poolSize": 65535 }],
  "dns": { "servers": ["fakedns"], "queryStrategy": "UseIPv4" },
  "inbounds": [{
    "tag": "socks-in",
    "protocol": "socks",
    "sniffing": {
      "enabled": true,
      "destOverride": ["fakedns", "http", "tls", "quic"],
      "routeOnly": false
    }
  }]
}

Три вещи, которые надо знать до того, как раскатывать это на клиентов:

  • sniffing сам по себе резолв не отменяет — он лишь читает домен из уже устанавливаемого соединения. Отмена внешнего DNS работает только в связке с fakedns;
  • routeOnly: true ломает приём: серверу уйдёт адрес, а не домен. С fakedns эта комбинация ломает выход в сеть совсем, потому что адресом назначения останется служебный адрес из пула;
  • у пула есть вытеснение по мере заполнения. Приложение, которое держит долгое соединение и кэширует адрес, может уехать не туда. Локальные и внутрисетевые домены из fakedns надо исключать.

3. Свой резолвер на своём сервере

Раз фильтр знает конкретные имена и адреса публичных резолверов, а не протокол — поднимите свой DoH на своём домене. Unbound или AdGuard Home, сертификат на ваш домен, эндпоинт известен только вашим клиентам.

Установка: три рабочих варианта

Отдельный сервер под это не нужен: резолвер на 100–500 клиентов укладывается в 1–2 vCPU и 1–2 ГБ памяти. AdGuard Home официально требует один поток процессора и 256 МБ памяти минимум, на практике съедает 50–200 МБ в зависимости от фильтр-листов. У Unbound кэш настраивается на поток, и это главное, за чем надо следить при росте.

Вариант 1: Unbound. Собственный DoH встроен начиная с версии 1.12.0, отдельный прокси не нужен — но сборка должна быть с --with-libnghttp2, иначе HTTP/2 просто не поднимется. Базовая часть конфига:

server:
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    qname-minimisation: yes
    interface: 203.0.113.10@443
    access-control: 10.8.0.0/24 allow
    tls-service-key: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
    tls-service-pem: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
    ip-ratelimit: 200

Разбор по строкам. auto-trust-anchor-file включает DNSSEC с автообновлением корневого ключа. interface с суффиксом @443 — это и есть DoH-слушатель, эндпоинт по умолчанию /dns-query (меняется директивой http-endpoint). access-controlсамое важное здесь: по умолчанию Unbound отвечает только локалхосту, и расширять надо ровно до своих подсетей, а не до 0.0.0.0/0. ip-ratelimit по умолчанию выключен (значение 0), его нужно включать руками.

Вариант 2: AdGuard Home. Веб-интерфейс вместо конфига, и заодно фильтрация рекламы для клиентов. Settings → Encryption: вставить содержимое fullchain.pem и privkey.pem, указать домен в поле Server name, сохранить. Эндпоинт /dns-query поднимается сам. DoH, DoT и DoQ поддерживаются из коробки.

Вариант 3: dnsdist. Если резолвер уже есть и нужен только шифрованный фронт к нему — одна строка:

addDOHLocal('203.0.113.10', '/etc/ssl/certs/dns.example.com.pem', '/etc/ssl/private/dns.example.com.key')

Начиная с версии 2.1.0 под капотом nghttp2. DoH поверх HTTP/3 поднимается отдельно, через addDOH3Local(). Knot Resolver 6 умеет то же самое через YAML: kind: doh2 в network.listen, а рядом kind: doq для DoQ.

Три вещи, на которых это ломают

Открытый резолвер. Резолвер, отвечающий всему интернету — это усилитель для DDoS-атак, и ваш сервер приедет в чужие блок-листы за неделю. access-control только на свои подсети, ip-ratelimit включён, рекурсия для чужих адресов выключена.

DNSSEC мимо кассы. Если ставите DoH-фронт отдельным демоном (например m13253/dns-over-https), помните: сам фронт подписи не проверяет. Валидация должна быть на Unbound или BIND позади него, иначе вы получили шифрованный канал до сервера, который сам верит чему попало.

EDNS Client Subnet. Механизм, передающий авторитетному серверу подсеть клиента, чтобы CDN отдал ближайший узел. Соблазнительно включить ради скорости — но исследования 2025 года значимой корреляции между этим и выигрышем в скорости не показали, а в части случаев выбор узла становился хуже. Для VPN-сервиса цена другая: вы частично сливаете CDN подсеть реального клиента. Держите выключенным или максимально усечённым.

Пошагово: Unbound с DoH за час

Ставим на чистый Ubuntu 24.04 или Debian 13. Домен для резолвера уже должен смотреть A-записью на этот сервер.

1. Пакет и корневой ключ.

apt update && apt install -y unbound knot-dnsutils
unbound -V | grep -i nghttp2

Вторая команда — проверка, что сборка умеет DoH. Нужна строка с --with-libnghttp2. Если её нет, DoH не поднимется, и придётся либо собирать вручную, либо брать AdGuard Home или dnsdist.

Корневой ключ для DNSSEC пакет создаёт сам. Если файла нет:

unbound-anchor -a /var/lib/unbound/root.key

2. Сертификат. Проще всего standalone, порт 80 на момент выпуска должен быть свободен:

snap install --classic certbot
ln -sf /snap/bin/certbot /usr/local/bin/certbot
certbot certonly --standalone -d dns.example.com

Если 80 занят или домен спрятан за Cloudflare — берите DNS-01, он не требует открытого порта вообще:

apt install -y python3-certbot-dns-cloudflare
certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d dns.example.com

В файле с ключами — одна строка, токен с правами Zone:DNS:Edit:

dns_cloudflare_api_token = ваш_токен

3. Права на сертификат. Вот здесь спотыкаются чаще всего. Unbound работает под пользователем unbound, а /etc/letsencrypt читает только root. Поэтому не подсовывайте Unbound пути от Let's Encrypt напрямую — копируйте файлы хуком:

mkdir -p /etc/unbound/tls
cat > /etc/letsencrypt/renewal-hooks/deploy/unbound.sh <<'SH'
#!/bin/bash
D=/etc/letsencrypt/live/dns.example.com
install -o unbound -g unbound -m 600 "$D/privkey.pem"   /etc/unbound/tls/privkey.pem
install -o unbound -g unbound -m 644 "$D/fullchain.pem" /etc/unbound/tls/fullchain.pem
systemctl restart unbound
SH
chmod +x /etc/letsencrypt/renewal-hooks/deploy/unbound.sh
/etc/letsencrypt/renewal-hooks/deploy/unbound.sh

Хук отработает и при каждом автопродлении, так что про истёкший сертификат можно забыть.

4. Конфиг. Кладём в /etc/unbound/unbound.conf.d/doh.conf — отдельным файлом, чтобы не спорить с пакетными сниппетами:

server:
    # Наружу торчит ТОЛЬКО DoH. Обычный DNS на 53 порту слушаем на локалхосте,
    # иначе получаем открытый резолвер и усилитель для DDoS-атак.
    interface: 127.0.0.1
    interface: 0.0.0.0@443
    interface: ::0@443
    access-control: 0.0.0.0/0 allow
    access-control: ::0/0 allow

    https-port: 443
    http-endpoint: "/dns-query"
    http-max-streams: 100
    tls-service-key: "/etc/unbound/tls/privkey.pem"
    tls-service-pem: "/etc/unbound/tls/fullchain.pem"

    # DNSSEC: ключ обновляется сам по RFC 5011
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    module-config: "validator iterator"
    harden-glue: yes
    harden-dnssec-stripped: yes
    qname-minimisation: yes

    # Кэш считается НА ПОТОК, а не на процесс: с num-threads 2 память удваивается
    num-threads: 2
    msg-cache-size: 64m
    rrset-cache-size: 128m
    cache-max-ttl: 86400
    prefetch: yes
    so-reuseport: yes

    # По умолчанию оба лимита выключены (0). Без них любой, кто узнал адрес,
    # может греть ваш сервер запросами столько, сколько захочет.
    ratelimit: 1000
    ip-ratelimit: 500

    # Не отвечаем на version.bind и id.server — не сообщаем версию тем, кто сканирует
    hide-identity: yes
    hide-version: yes
    verbosity: 1

access-control: 0.0.0.0/0 allow здесь безопасен именно потому, что публично слушается только порт 443 с TLS: обычного UDP/53 наружу нет. Если решите открыть и 53 — сузьте до своих подсетей, иначе через сутки приедете в блок-листы.

5. Проверка и запуск.

unbound-checkconf && systemctl restart unbound && systemctl status unbound --no-pager

unbound-checkconf молчит при успехе и выходит с кодом 1 при ошибке. Дальше — автозапуск и потолок по памяти:

systemctl enable unbound
mkdir -p /etc/systemd/system/unbound.service.d
printf '[Service]\nRestart=on-failure\nRestartSec=5\nMemoryMax=512M\n' \
  > /etc/systemd/system/unbound.service.d/override.conf
systemctl daemon-reload && systemctl restart unbound

6. Файрвол.

ufw default deny incoming
ufw allow OpenSSH
ufw allow 443/tcp
ufw enable

7. Убедиться, что работает. С любой другой машины:

kdig @dns.example.com +https A example.com

В ответе должен быть блок ;; ANSWER SECTION с адресом. Проверка DNSSEC — флаг ad в строке флагов:

kdig @dns.example.com +https +dnssec A example.com | grep -i flags

И обязательно — проверка, что вы не открытый резолвер. Тоже со стороннего хоста:

dig +short @ВАШ_IP version.bind CHAOS TXT

Правильный ответ здесь — таймаут или пустота. Если пришла версия Unbound, порт 53 торчит наружу и это надо чинить прямо сейчас.

AdGuard Home, если не хочется конфигов

Тот же результат через веб-интерфейс, плюс фильтрация рекламы клиентам в подарок.

curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v

Ставится в /opt/AdGuardHome, мастер первичной настройки — на порту 3000. Дальше: Settings → Encryption, три поля — Certificates (содержимое fullchain.pem), Private key (содержимое privkey.pem), Server name (домен). Сохранить.

Если 443 уже занят, порт меняется не в интерфейсе, а в /opt/AdGuardHome/AdGuardHome.yaml, секция tls, ключ port_https. Останавливаете службу, правите, запускаете. Сертификаты AdGuard Home перечитывает сам при изменении файлов.

Разобранный пример: как это собрано в живом сервисе

{
  "dns": {
    "hosts": {
      "dns.example.com":  ["203.0.113.10"],
      "dns2.example.com": ["203.0.113.11"],
      "common.dot.dns.yandex.net": "77.88.8.8"
    },
    "servers": [
      { "address": "https://common.dot.dns.yandex.net/dns-query",
        "domains": ["regexp:\\.ru$", "regexp:\\.рф$", "domain:sber.world", "... ещё сотни доменов"] },
      "8.8.8.8",
      "https://dns.example.com:8443/dns-query",
      "https://dns2.example.com:8443/dns-query",
      "https://1.1.1.1/dns-query"
    ],
    "queryStrategy": "UseIPv4",
    "disableCache": false,
    "tag": "dns_q"
  }
}

Что здесь сделано правильно:

Два своих резолвера, а не один. Оба прибиты в hosts, поэтому циркулярка разорвана с резервом: упал первый — второй отвечает, и обоим не нужен внешний DNS, чтобы их найти.

DoH на порту 8443, а не 443. На той машине 443 занят под сам VPN. Порт для DoH не догма: клиент берёт его прямо из адреса в конфиге.

Российские домены — российскому резолверу. Отдельный сервер с длинным списком: .ru, .рф, .su и поимённо банки, госуслуги, платёжки. Смысл двойной. Во-первых, эти домены не ломаются, когда весь остальной трафик уходит через заграницу. Во-вторых, российские CDN отдают ближайший узел, а не европейский — сайты открываются быстрее.

Вторая строка выглядит ошибкой. Это не ошибка

Голый 8.8.8.8 по UDP стоит выше собственных DoH-эндпоинтов. Читается это так: основной поток запросов идёт открытым UDP на публичный адрес, то есть ровно под текущий перехват, а свои резолверы включаются только когда он молчит.

Именно так я эту строку сначала и прочитал. И ошибся. Разбор ошибки здесь полезнее самого конфига, потому что ошибка типовая: секцию dns нельзя понять, глядя только на секцию dns.

Безопасной эту строку делают две вещи, которых в ней не видно.

Первое: DNS уходит в туннель раньше всего остального. В маршрутизации самым первым правилом стоит заворот dns_q в балансировщик. Запрос не идёт по российским сетям вообще — он едет зашифрованным внутри VLESS до зарубежной ноды и превращается в обычный DNS уже там, за пределами досягаемости ТСПУ. Перехватывать нечего.

Второе: на каждой ноде стоит перехват DNS. Пара правил iptables заворачивает весь DNS-трафик на локальный Unbound. Проверяется одной командой:

dig @8.8.8.8 example.com +short
dig @127.0.0.1 example.com +short

Если ответы идентичны — перехват работает, и запрос к 8.8.8.8 физически никуда не уходит: его обслуживает свой резолвер на ноде выхода. В разобранном сервисе счётчики Unbound показывают по 4–5 миллионов обработанных запросов на ноду. Это не остаток, это весь поток.

То есть 8.8.8.8 здесь — не публичный резолвер, а адрес-заглушка, по которому клиент попадает на свой же.

Зачем тогда DoH-эндпоинты и почему они не первые

Казалось бы, честнее поставить свой DoH первым. Замер говорит обратное:

Домен DoH первым Локальный резолв первым
microsoft.com 837 мс 546 мс
wikipedia.org 583 мс 469 мс

Причина простая: DoH-эндпоинты стоят на одних нодах, а выход клиента может идти через другие. Получается лишний хоп на каждый новый домен. Резолв на ноде выхода даёт ровно то же самое — свой резолвер, полный контроль, шифрование внутри туннеля — но без этого хопа.

DoH-эндпоинты в списке ниже работают резервом: если на ноде перехват не сработал, очередь дойдёт до них. Это правильное для них место.

Что отсюда забрать

Порядок серверов — логика, а не оформление, это по-прежнему так. Но прежде чем менять его, посмотрите две вещи за пределами секции dns: куда маршрутизация отправляет dns_q, и что стоит на нодах. Один и тот же список серверов означает разное в зависимости от ответов.

И проверяйте замером, а не рассуждением. Схема выше выглядит уязвимой и не является таковой; обратное тоже бывает.

Настоящая слабость этой схемы в другом. Она держится на том, что на каждой ноде не забыли правила перехвата. Это ручной шаг, который ничем не проверяется: развернули новую ноду, не поставили iptables — и она тихо начнёт резолвить мимо. Лечится не перестановкой строк в конфиге, а проверкой перехвата прямо в скрипте развёртывания ноды: не прошла проверка — нода не попадает в пул.

Циркулярка: почему это не заводится с первого раза

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

Ваш резолвер живёт на домене — скажем, dns.example.com. Чтобы к нему обратиться, клиенту нужно узнать его адрес. Узнать адрес — это DNS-запрос. А DNS у клиента сейчас как раз и сломан: именно поэтому вы и поднимаете свой резолвер.

Получается замкнутый круг: чтобы починить DNS, нужен работающий DNS.

Пока сеть в порядке, круг незаметен — системный резолвер отвечает, всё работает, вы уходите довольным. Ломается оно у клиента и в худший момент: под перехватом системный резолвер либо не отвечает, либо отдаёт подменённый адрес, и клиент не может достучаться до вашего резолвера, чтобы тот его спас.

Разрывается круг одной строкой — адрес самого резолвера прибивается гвоздями:

{
  "dns": {
    "hosts": {
      "dns.example.com": ["203.0.113.10"]
    },
    "queryStrategy": "UseIPv4",
    "servers": [
      "https://dns.example.com/dns-query",
      "https://1.1.1.1/dns-query"
    ],
    "tag": "dns_q"
  }
}

Что здесь важно, по строчкам.

hosts идёт первым и решает всё. Адрес вашего резолвера берётся из конфига, а не из сети. Круг разорван: клиенту больше не нужен рабочий DNS, чтобы добраться до DNS.

Второй сервер в списке — не украшение. Свой резолвер это ещё один сервис, который может лечь: кончится сертификат, упадёт контейнер, хостер выключит машину. Без запасного клиент в этот момент останется вообще без резолвинга. Публичный DoH вторым номером — страховка на этот случай, а не дублирование.

UseIPv4 — по той же причине, что и везде в этой статье: на ноде без честного IPv6 ответы с AAAA уводят часть трафика в никуда.

tag нужен, чтобы завернуть сами DNS-запросы в туннель правилом маршрутизации — это первый способ из этой статьи, они складываются.

Если резолвер стоит за CDN

Схема совместима с CDN-фронтингом, но на хосте, который смотрит на CDN, нужно прописать sockopt. Без него соединение до резолвера идёт мимо той логики, ради которой CDN и ставили.

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

Клиенты: два тумблера, без которых не работает

HAPP игнорирует вашу секцию dns, пока не включён тумблер — про это подробно выше, в разборе народного рецепта. Со своим резолвером цена ошибки выше: клиент будет ходить в DoH Cloudflare, ваш резолвер вообще не увидит запросов, а вы будете искать поломку на сервере.

На десктопе, если клиенту нужен обход через CDN, приходится переключать режим туннелирования на Xray TUN. Это второй костыль, и он про другую ситуацию, чем первый: тумблер отдаёт управление DNS вашему конфигу, TUN меняет способ захвата трафика.

Что даёт на практике

Из полевого отчёта участника сообщества, поднявшего схему на одной ноде: пинги в клиенте 131–262 мс против ожидавшихся трёхсот, домены популярных сервисов резолвятся мгновенно, CDN через эту же схему работает.

Оговорка, которая тут обязательна. Это чужой замер на одной ноде, а не наш стенд. Своих измерений именно этой схемы под текущей волной у нас по-прежнему нет — в отличие от первых двух способов, которые мы гоняли на стенде и показали логами выше. Логика схемы прозрачна и подтверждается практикой, но ТСПУ умеет и поведенческие сигнатуры: свой DoH-эндпоинт с постоянным клиентским пулом со временем может стать заметным сам по себе.

И помните про эксплуатацию: это ещё один сервис, который надо держать живым, обновлять и мониторить. Упавший резолвер без запасного в servers ломает доступ всем вашим клиентам разом.

4. DNS по TCP — временное окно

Работает прямо сейчас: tcp://1.1.1.1:53 вместо 1.1.1.1. Перехватывают UDP, TCP проходит.

Относитесь к этому как к пластырю, а не к решению. UDP уже режут, TCP — следующий очевидный шаг, и когда его закроют, всё сломается снова. Как основной механизм не годится; как быстрое лечение прямо сегодня — вполне.

5. Прибить адрес сервера гвоздями

Против первой стадии, самое простое. Секция hosts в конфиге Xray жёстко сопоставляет домен адресу, и резолв адреса вашей ноды перестаёт зависеть от чего бы то ни было:

{
  "dns": {
    "hosts": { "node1.example.com": ["203.0.113.10"] },
    "servers": ["tcp://1.1.1.1:53"]
  }
}

У HAPP для этого есть отдельное штатное поле в профиле маршрутизации. Плюс перед голым IP в поле «Адрес» — домен остаётся доменом, переезд по-прежнему делается одной A-записью, а клиент при этом не зависит от системного резолвера.

Конструктор: соберите свою секцию dns

Выберите стратегию и подставьте свои данные — получите готовый фрагмент, который вставляется в шаблон XRAY_JSON вашей панели. Конструктор подсказывает грабли по ходу: правило маршрутизации, которое должно стоять первым, IPv6, который сломает половину сайтов, и резолвер, который сейчас режут.

Конструктор DNS-секции для XRAY_JSON

Заполните поля — получите готовый фрагмент для шаблона подписки в панели. Ничего никуда не отправляется, всё считается прямо в браузере.

Запросы уедут внутрь шифрованного туннеля, снаружи их не видно.
Суффикс +local здесь не используется намеренно — он идёт мимо маршрутизации, и завернуть его в туннель невозможно.
Тот, что стоит в поле «Адрес» хоста в панели.
Резолв адреса ноды перестанет зависеть от системного DNS.
Как называется ваш VLESS-аутбаунд в шаблоне: proxy, vless-out и т.п.
На нодах без нормального IPv6 берите UseIPv4, иначе часть сайтов уедет в никуда.

Клиенты: кто слушается вашего конфига

Вы можете написать идеальную секцию dns в шаблоне подписки — и половина клиентов её проигнорирует. Это не баг, это архитектура: большинство приложений не отдаёт ядру ваш JSON дословно, а собирает свой поверх настроек в интерфейсе.

Клиент Уважает dns из подписки Чем резолвит, если игнорирует Отдельный резолв адреса сервера
INCY Да, если секция непустая достраивает только стратегию
sing-box (официальный) Да, профиль это и есть конфиг ядра domain_resolver
HAPP Только при включённом тумблере и полном JSON свой профиль, по умолчанию DoH Cloudflare есть, отдельное поле
v2rayNG Нет, всегда своё поле, по умолчанию 1.1.1.1
Hiddify Нет, генерирует свой поверх профиля по умолчанию 1.1.1.1 вне Китая
Karing Нет, свои четыре типа серверов свой fallback есть, отдельный тип
Clash Verge В TUN-режиме принудительно свой fake-ip, отключить профилем нельзя proxy-server-nameserver
FlClash Да, по умолчанию не трогает proxy-server-nameserver

Что из этого следует практически:

v2rayNG — худший случай. Секция dns игнорируется всегда, а поле резолвера в настройках самовосстанавливается в 1.1.1.1 после очистки. Пользователь физически не может убрать опасный дефолт, даже если знает про проблему. Клиентам на v2rayNG остаётся только объяснять руками, куда нажать.

Hiddify по умолчанию ставит 1.1.1.1 для всех регионов кроме Китая — отдельного значения для России нет. Клиент, который ни разу не заходил в настройки DNS, поедет ровно на тот резолвер, который сейчас фильтруют.

Официальный sing-box с версии 1.12 требует секцию dns практически обязательно: без неё падает резолв самого адреса сервера. Это легко принять за блокировку, хотя причина — миграция полей в ядре. Если у вас клиенты на sing-box и жалобы начались после обновления приложения, проверьте это до того, как винить ТСПУ.

Про десктопы. На Android и iOS система заворачивает DNS в туннель декларативно, через штатный API. На Windows и macOS такого механизма нет — DNS-настройки адаптера надо перехватывать вручную, и делает это каждый клиент по-своему, с разным качеством. Поэтому жалобы «на телефоне работает, на компьютере нет» — норма для этой волны, а не странность.

Ни одно из трёх ядер — ни Xray, ни sing-box, ни mihomo — не поддерживает DNSCrypt. Если вам его советуют как обход, речь про отдельный локальный демон, а не про настройку в конфиге.

У Xray нет DoT. В секции dns можно указать обычный резолвер, TCP, DoH и DoQ — но tls:// как тип DNS-сервера ядро не поддерживает. А DoQ доступен только в варианте +local, то есть заведомо мимо туннеля. Для Xray-конфигов выбор фактически между обычным UDP, TCP и DoH.

Как проверять

Половина неверных диагнозов последних дней — результат замера не с той машины и не тем способом.

Ловушка, на которой спотыкаются все. Замер с машины, где включён VPN, не значит ничего. Мы поймали это на себе: тест уверенно показал «UDP молчит, TCP отвечает — блокировка», хотя UDP глушил сам VPN-клиент, а не провайдер. Отключите туннель или явно привяжите проверку к физическому интерфейсу.

ловушка: замер с включённым VPN врёт
~ $ ./dnscheck.sh udp-vs-tcp rutracker.org 8.8.8.8
[WARN] дефолтный маршрут идёт через 'utun10' — на этой машине АКТИВЕН туннель.
       Замеры покажут блокировки самого VPN-клиента, а не провайдера.
[INFO] 1) UDP vs TCP: rutracker.org через 8.8.8.8
  dig  UDP: <нет ответа>
  dig  TCP: 104.21.32.39,172.67.182.196
[WARN] UDP молчит, TCP отвечает — типично для блокировки UDP:53
──────── тот же тест, но мимо туннеля ────────
~ $ DNSCHECK_SRC=$(ipconfig getifaddr en0) ./dnscheck.sh udp-vs-tcp rutracker.org 8.8.8.8
  dig  UDP: 104.21.32.39,172.67.182.196
  dig  TCP: 104.21.32.39,172.67.182.196
[OK]   UDP и TCP совпадают
   Данные те же, вердикт противоположный. Разница только в интерфейсе.

Минимальная проверка на перехват — сравнить UDP и TCP:

dig +short rutracker.org @8.8.8.8; dig +short +tcp rutracker.org @8.8.8.8

Расходятся или UDP отдаёт NXDOMAIN при живом TCP — перехват есть. Совпали — на этой сети проблема в другом.

Более строгая проверка, которая ловит перехват независимо от того, какие домены подменяют:

dig +time=4 +tries=1 rutracker.org @192.0.2.1

192.0.2.1 — документационный адрес из RFC 5737, DNS-сервера там нет нигде в мире. Правильный ответ на эту команду — таймаут. Если пришёл настоящий ответ, значит на пути стоит перехватчик, отвечающий за любой адрес.

Ещё одна тонкость: не сравнивайте ответы по IP-адресам. Google и YouTube за CDN отдают разные адреса при каждом запросе, и вы получите ложные «расхождения». Сравнивайте статус ответа (NOERROR против NXDOMAIN) или берите домен со стабильными записями.

Проверить, что DNS реально идёт через туннель, а не мимо, можно по логу ядра. Включите "loglevel": "debug" и ищите строки: dialing to означает, что запрос ушёл напрямую, tunneling request to — что он поехал в туннель. Это самый честный ответ на вопрос «а точно ли мой конфиг работает».

Куда это идёт

30 апреля 2026 года Минцифры выложило на общественное обсуждение проект приказа с требованиями к средствам выявления сетевых адресов, соответствующих доменным именам. В тексте прямо названы TLS 1.3 с ESNI, DoH и DoT — как факторы, снижающие эффективность фильтрации. Проект обязывает операторов взаимодействовать с НСДИ и хранить данные о резолвинге в течение года; смета — свыше 3 млрд рублей. Принят он или нет, по открытым источникам на конец августа подтвердить не удалось.

Читать это стоит так: шифрованный DNS перестал быть незамеченной лазейкой и попал в нормативные документы под собственным именем. Схема «поставлю чужой публичный DoH и забуду» будет ломаться и дальше — не потому, что кто-то целится в вас, а потому, что публичные резолверы это конечный и известный список. Свой резолвер на своём домене выигрывает ровно тем, что в этот список не входит.

Обратная сторона, о которой честнее сказать вслух: ТСПУ умеет поведенческие сигнатуры, и свой DoH с постоянным пулом клиентов со временем может стать заметным сам по себе. Данных о том, что это уже происходит, у нас нет.

Чеклист: что сделать сегодня

  1. Проверьте, есть ли у вас в шаблоне CIDR-правила. Если да — ваши клиенты зависят от DNS, и всё нижеследующее для вас критично. Если правила чисто доменные, вы, скорее всего, волну и не заметили.
  2. Загляните в секцию dns шаблона подписки. Стоит там голый 1.1.1.1 или 8.8.8.8 по UDP — это ровно то, что сейчас перехватывают. Меняйте.
  3. Проверьте суффикс +local. Если он есть, DNS никогда не пойдёт в туннель, что бы вы ни писали в правилах.
  4. Заверните DNS в туннель через tag и правило маршрутизации первым в списке.
  5. Прибейте адрес нод через hosts — это снимает первую стадию, не требуя перехода на голые IP в панели.
  6. Выясните, чем пользуются ваши клиенты. Для v2rayNG и Hiddify конфигом проблему не решить, нужна инструкция в поддержку.
  7. Не переводите хосты на голые IP как постоянное решение. Разово — можно, навсегда — вы себе же усложните следующий переезд.
  8. Требуйте замер с машины клиента. Ваш сервер в дата-центре про эту волну ничего не знает.

Чего мы не проверили

Чтобы вы понимали границы применимости написанного.

У нас нет замера с проблемного домашнего провайдера — все три точки оказались чистыми. Всё, что здесь сказано про поведение Ростелекома и Дом.ру, взято из публичных замеров и сообщений, а не из собственных наблюдений. Как только доберёмся до такой точки, дополним статью фактурой.

Не проверяли под текущей волной: DoQ как протокол, DNSCrypt, Quad9, а также устойчивость собственного DoH-резолвера к детекту по TLS-отпечатку. По этим пунктам у нас нет ни подтверждений, ни опровержений — и мы предпочитаем сказать это прямо, а не выдать догадку за проверенное.