Все системы работают штатно 6 офшорных регионов Оформление без KYC
Hands-on Полевое руководство

Свой DNS-резолвер на VPS: лог, который не ведёт никто другой

Вы зашифровали трафик, перенесли машину в офшор и заплатили в Monero. А потом ввели доменное имя, и прежде чем хоть один зашифрованный байт покинул машину, ваше устройство спросило незнакомца, куда его отправить, — и хранить этот вопрос досталось именно этому незнакомцу. Лог резолвера — самое полное описание человека, какое вообще существует в сети: каждый сайт, каждое приложение, каждая фоновая служба, по порядку, с метками времени. Это руководство переносит этот лог на машину, которую вы арендуете сами, — рекурсивный, DNSSEC-валидирующий Unbound на самом дешёвом тарифе из списка, доступный только через ваш собственный туннель и, что большинство статей обходят стороной, не открытый резолвер, который кто-то другой сможет направить на жертву.

Обновлено 2026-09-15 · 15 мин чтения · Операции с парком серверов
На этой странице
  1. Лог, который описывает вас лучше, чем история браузера
  2. Форвардинг переносит лог. Рекурсия дробит его.
  3. Что это скрывает, а что — совершенно точно нет
  4. Ошибка, которая превращает ваш резолвер в чужое оружие
  5. Настройки Unbound по умолчанию разумны. Но не приватны.
  6. Два способа подключиться: туннель или DNS-over-TLS
  7. Блокировка — это другой продукт; решите заранее, а не когда она уже прикручена
  8. Резолвер, который вы запускаете, — зависимость, которая принадлежит вам
  9. Шаг за шагом
SP·01

Лог, который описывает вас лучше, чем история браузера

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

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

Сегодня этой третьей стороной оказывается тот, на кого указал ваш DHCP-лиз, — ваш провайдер, ваш работодатель, кафе с wi-fi. Те, кто это замечает, обычно переходят на публичный резолвер, а это реальный шаг вперёд по части добросовестности и шаг в сторону — не вперёд — по части приватности: вы не убрали наблюдателя, вы просто сменили компанию, обменяв зарегулированного телекома на CDN, живущий рядом с рекламным бизнесом, или некоммерческую организацию, чья политика может измениться вместе с её финансированием. Добросовестные операторы публикуют честные политики хранения, и некоторые действительно их соблюдают. Но политика — это обещание о поведении, а конфигурационный файл — это утверждение о возможностях. Обещание нельзя проверить аудитом. Двадцать строк, которые вы написали сами, на машине, которую вы арендуете, в стране, которую вы выбрали, — можно.

SP·02

Форвардинг переносит лог. Рекурсия дробит его.

Почти любая статья, которая обещает «запусти свой собственный DNS-сервер», в итоге строит форвардер: небольшой кэш в вашей сети, который передаёт всё незнакомое на 1.1.1.1 или 9.9.9.9. Штука по-настоящему полезная — это быстро, это пять строк, и это отучает вашу локальную сеть следить за вами. Но совокупный лог она оставляет ровно там же, где он и был. Каждое имя, которое вы ищете, всё равно приходит в одну и ту же компанию, теперь удобно подписанное единственным IP-адресом вашего резолвера, — от этого поток становится легче атрибутировать, а не труднее.

Рекурсивный резолвер делает эту работу сам, а не перекладывает её на кого-то. Получив запрос про news.example.io, он спрашивает корневой сервер, кто отвечает за .io, затем спрашивает серверы .io, кто отвечает за example.io, и только потом обращается напрямую к этому оператору. Три разговора с тремя не связанными друг с другом сторонами, ни одна из которых не занимается агрегацией, и — это здесь самое важное — ни одна из них никогда не видит ваш запросный поток целиком. Операторы корневых серверов видят тонкий ручеёк запросов к TLD. Verisign видит, что кто-то по вашему адресу тронул что-то в зоне .com. Авторитативный сервер конкретного домена видит тот трафик, который увидел бы в любом случае, — вы всё равно собираетесь к нему подключиться.

QNAME-минимизация обостряет этот эффект значительно, и современный Unbound делает это по умолчанию. Вместо того чтобы отправлять всё имя целиком каждому серверу в цепочке — а именно так резолверы поступали тридцать лет подряд, — он отправляет каждому серверу только ту метку, которая нужна для ответа: io. — корню, example.io. — серверам .io, и только затем полное news.example.io. — тому оператору, которому оно действительно причитается. Иерархия перестаёт быть трансляцией ваших намерений всем подряд и становится тем, чем и была задумана: делегированием.

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

SP·03

Что это скрывает, а что — совершенно точно нет

Честные рамки этой меры куда уже, чем предполагает маркетинг вокруг приватного DNS, и именно знание этих границ не даёт принять плохое решение на одной лишь силе хорошего чувства.

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

Это не скрывает само соединение. После того как имя разрешилось, ваше устройство всё равно открывает сессию к этому адресу, и любой, кто смотрит на ваш аплинк, видит IP назначения. Для сайта на выделенной инфраструктуре IP-адрес и есть идентификатор. Не скрывает это и имя хоста на проводе: если обе стороны не поддерживают Encrypted Client Hello, TLS-хендшейк всё равно несёт имя сервера в открытом виде — ту же самую информацию, которая была бы у вашего резолвера.

Не скрывает это и запросы от вашего хостинг-провайдера. Вот здесь чаще всего ошибаются, так что стоит сказать это без оговорок: исходящий трафик вашего резолвера — вопросы, которые он задаёт корневым, TLD- и авторитативным серверам, — покидает VPS по UDP-порту 53 в незашифрованном виде, и сеть, в которой сидит ваш сервер, может прочитать всё это целиком. До корневых серверов никакого DoT не существует. Вы не удалили наблюдателя, а скорее переместили его — от розничного провайдера, который продаёт данные и отвечает на повестки в вашей собственной стране, к хостинг-сети в юрисдикции, которую вы выбрали осознанно, и которая благодаря QNAME-минимизации видит лишь раздробленный поток. Это настоящее улучшение, и это вопрос о том, чей закон действует на этом отрезке провода, а не то, что может решить конфигурационный файл.

И это не даёт вам толпы. Резолвер, которым пользуется одно домохозяйство, без всякой неоднозначности приписывает каждый запрос именно этому домохозяйству. Против глобального пассивного наблюдателя оживлённый общий резолвер и правда прячет лучше. Против вашего провайдера, вашего работодателя, дата-брокеров, покупающих телеметрию резолверов, и обычных массовых запросов, которые на самом деле случаются с обычными людьми, — ваш резолвер лучше, но только если последний участок от ваших устройств идёт внутри туннеля. Используйте это вместе с вашим собственным туннелем WireGuard, а не вместо него. Сам по себе приватный резолвер по большей части просто переносит ваши метаданные. За туннелем он закрывает тот единственный канал, который туннель оставляет открытым.

SP·04

Ошибка, которая превращает ваш резолвер в чужое оружие

Здесь есть ровно один способ всё испортить по-крупному, допустить его легко и случайно, а последствия достаются посторонним раньше, чем вам самим.

Резолвер, который отвечает всем подряд, — это открытый резолвер, а открытый резолвер — это усилитель. DNS работает по UDP, исходный адрес в UDP подделывается тривиально, а маленький запрос способен породить ответ, во много раз превышающий его размер. Атакующий отправляет вашей машине 60-байтовый вопрос с адресом жертвы, подделанным в качестве отправителя; ваша машина исполнительно отправляет жертве ответ, в несколько десятков раз крупнее. Сделайте это одновременно с нескольких тысяч открытых резолверов — и жертва падает с интернета, получив поток, который на уровне пакетов совершенно точно выглядит так, будто идёт от вас. Мишень не вы. Вы — оружие, и трафик в отчёте об инциденте — ваш.

Дальше — не самое красивое: жалобы на абьюз от сетей, о которых вы никогда не слышали, провайдер, который занулит маршрут до вашего адреса, чтобы защитить собственный транзит, и разговор с поддержкой, которого вы предпочли бы не иметь. Вы оказываетесь на стороне, которая вызывает то самое событие, что описано в нашем runbook на первый час DDoS-атаки, и ни в одной версии этой истории ваш аптайм не выживает.

От этого защищают два независимых замка, и нужны оба, потому что каждый закрывает провал другого. Первый — это собственный access-control в Unbound: он должен refuse весь интернет по обоим семействам IP, а затем явно allow loopback и подсеть вашего туннеля, — список default-deny, а не allow-список с разрешающим хвостом. Второй замок — это то, слушает ли демон хоть что-то вовне: привяжите его к 127.0.0.1 и адресу туннеля, никогда к 0.0.0.0, и держите порт 53 закрытым на публичном интерфейсе на уровне firewall.

Ловушка — сделать только первое. Правило access-control не заставляет порт исчезнуть: отклонённый запрос — это всё равно принятый пакет и отправленный пакет, ваш адрес всё равно всплывает в сканах, которые составляют карту открытых резолверов, а одна последующая правка не той секции превращает отказ в ответ. Привязка и firewall делают эту ошибку структурно невозможной, а не расстоянием в одну строку конфига. Если на этой же машине крутятся контейнеры, перечитайте как Docker публикует порты в обход вашего firewall, прежде чем считать, что написанное вами правило и есть правило, которое действует.

SP·05

Настройки Unbound по умолчанию разумны. Но не приватны.

Unbound поставляется настроенным на корректность и стабильность — и это правильный дефолт для программы, которую в основном разворачивают провайдеры. Горстка настроек превращает его в инструмент, построенный для того, кто его запускает. Ни одна из них не экзотична — просто из коробки они выключены или оставлены без определённой позиции.

Перестаньте отвечать на вопросы о самом себе. По умолчанию резолвер с радостью сообщит версию своего ПО и имя хоста через класс CHAOSversion.bind и hostname.bind — а это бесплатная разведка для любого, кто решает, стоит ли ваша машина внимания. hide-identity и hide-version ничего не стоят и убирают этот отпечаток.

Отказывай закрыто, а не открыто. DNSSEC-валидация — это разница между резолвером, который обнаруживает подделанный ответ, и резолвером, который его отдаёт. harden-dnssec-stripped отказывается принимать неподписанный ответ для зоны, которая должна быть подписана, harden-glue и harden-below-nxdomain закрывают два классических вектора отравления кэша, а aggressive-nsec позволяет резолверу отвечать на несуществующие имена прямо из кэшированных записей отказа, не переспрашивая снова. use-caps-for-id добавляет случайный регистр букв как дополнительную энтропию против слепого спуфинга — это ничего не стоит, но иногда несовместимо с плохо собранным авторитативным сервером, и это стоит знать заранее, прежде чем вы потратите вечер на домен, который не резолвится.

Не записывайте ничего. Unbound не логирует запросы, если его не попросить, но настройки, которые включат это, — всего одна незакомментированная строка, а некоторые сборки дистрибутивов поставляются с более разговорчивым дефолтом. Задайте verbosity: 0 и явно укажите log-queries: no, чтобы намерение было видно прямо в файле, а не выводилось из его отсутствия. И не забудьте про часть, которая настройкой не является: сам кэш — это тоже запись. unbound-control dump_cache на работающей машине печатает недавнюю историю того, что эта машина искала, и живёт она в памяти машины, до которой могут дотянуться чужие руки. Она угасает сама по себе, и именно поэтому короткое время жизни кэша и приватность находятся в лёгком напряжении друг с другом, — и именно поэтому не стоит держать резолвер живым месяцами на хосте, которому вы совсем не доверяете.

Кэшируйте осознанно. Каждый ответ, отданный из кэша, — это наблюдение, которое никогда не происходит выше по цепочке, так что здоровый кэш — это не только про скорость, но и про приватность. prefetch обновляет популярные записи до того, как они истекут, так что типичный случай вообще перестаёт трогать сеть; serve-expired удерживает вас на связи, когда авторитативный сервер ненадолго недоступен. Повышение cache-min-ttl дальше снижает болтливость наверх, но при этом перекрывает осознанные решения операторов доменов — низкие TTL — это именно то, на чём работают CDN и failover, — так что порог в минуту-две разумен, а порог в час рано или поздно оставит вас на мёртвом адресе.

SP·06

Два способа подключиться: туннель или DNS-over-TLS

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

Для почти всех лучший ответ — это туннель. Если ноутбук и телефон уже держат сессию WireGuard до этого сервера, резолвер может слушать на адресе туннеля и говорить обычным DNS по порту 53. Трафик уже зашифрован и уже аутентифицирован туннелем, так что не нужно получать сертификат, не нужно открывать новый порт в интернет, не нужно выставлять стек TLS напоказ незнакомцам, и — вот недооценённая часть — имени хоста нигде вообще нет. Руководство по WireGuard на этом сайте оставляет у клиентов DNS = 9.9.9.9 именно потому, что раньше туда просто нечего было поставить лучше. Вот что встаёт в эту строку вместо этого: адрес туннеля вашего собственного сервера.

DNS-over-TLS — это для устройства, которое не может держать туннель. Поле Private DNS в Android — самый весомый аргумент в его пользу: оно работает системно, переживает перезагрузки и покрывает приложения, до которых иначе не дотянуться. Цена — это имя хоста с действительным сертификатом, а сертификат означает публичную, постоянную запись в логах Certificate Transparency, привязывающую это имя к моменту, когда вы его создали. Дальше passive DNS свяжет это имя с адресом сервера. Если смысл всего упражнения был в том, чтобы не оставлять своё имя на инфраструктуре, используйте имя хоста, которое никак не ведёт к вам, и регистрируйте его с той же осторожностью, что описана в материале про анонимную регистрацию домена, — а не поддомен домена, которым вы пользуетесь для всего остального: это навечно связывает их друг с другом.

Есть и вторая, более тонкая цена. Эндпоинт DoT, до которого дотягиваются роуминговые телефоны, невозможно ограничить по адресу отправителя, так что по определению это резолвер, которым сможет воспользоваться кто угодно посторонний, если узнает имя. Это не усилитель — TLS через TCP требует завершённого хендшейка, так что адрес отправителя невозможно подделать и нечего отражать, — но это раздаваемая вами ёмкость и сервис, который стоит просканировать. Держите включёнными лимиты запросов на IP, выбирайте имя хоста, которое никто не угадает, и относитесь к этому как к осознанному исключению для одного-двух устройств, а не как к двери по умолчанию.

DNS-over-HTTPS — это третий вариант, и здесь он обычно неверный. Ему нужен веб-сервер перед резолвером, а это больше подвижных частей и больше поверхности атаки — ради единственного преимущества быть неотличимым от обычного веб-трафика. Это преимущество решающее, если вы обходите сеть, блокирующую DoT, и неважное, если это не ваш случай.

SP·07

Блокировка — это другой продукт; решите заранее, а не когда она уже прикручена

Рано или поздно кто-то предложит добавить блоклисты, и предложение действительно выглядит привлекательно: фильтрация на уровне резолвера покрывает каждое устройство в туннеле, включая smart TV и приложения на телефоне, куда никакое расширение браузера не дотянется. На мобильных это вообще единственное практическое место для вмешательства. Это же и та функция, которая с наибольшей вероятностью заставит вас тихо перестать доверять собственной инфраструктуре, и разобраться, почему именно, стоит заранее, а не по ходу дела.

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

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

Если всё же хочется, предпочтите маленькую локальную зону в Unbound второму демону — local-zone: "tracker.example." always_nxdomain не требует ни лишнего софта, ни веб-интерфейса на порту, который потом придётся защищать, ни нового сервиса, который может упасть и утащить с собой резолвинг имён. Держите должностную инструкцию этой машины в одну строку: она резолвит имена. Каждая дополнительная обязанность, которую вы на неё вешаете, — это ещё один способ для всего сразу перестать работать.

SP·08

Резолвер, который вы запускаете, — зависимость, которая принадлежит вам

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

Реалистичные режимы отказа стоит знать заранее, потому что у каждого свой почерк. VPS перезагружается, а Unbound никогда не был включён в автозагрузку — так что всё работает до первого незапланированного рестарта. Большой блоклист выталкивает кэш в swap на инстансе с 1 ГБ памяти, и OOM-killer выбирает жертвой именно резолвер. Где-то в зоне ломаются собственные подписи DNSSEC, и ваш правильно настроенный резолвер отказывается отдавать ответ, пока все, кто сидит на невалидирующем резолвере, продолжают спокойно бродить по сайту, — ваша машина права, а сайт всё равно выглядит для вас сломанным, и это сбивающие с толку пять минут, если вы успели забыть, что у вас включена валидация. Или отваливается туннель, и поскольку DNS = 10.66.0.1 существует только внутри туннеля, у устройства не остаётся вообще никакого резолвера, и оно сообщает, что оффлайн.

Смягчить это на удивление дёшево. Включите сервис в автозагрузку и реально проверьте это перезагрузкой, а не предположением. Дайте клиентам второй, резервный резолвер, чтобы мёртвый туннель деградировал, а не останавливал всё, — публичный резолвер в этой роли — это маленький, явный компромисс по приватности, который действует только тогда, когда ваш собственный недостижим, и обычно это правильный обмен. Держите serve-expired включённым, чтобы короткая авария выше по цепочке не становилась вашей аварией. Проверяйте резолвер откуда-то ещё, а не с самой машины, — это единственный способ заметить разницу между «упал» и «недостижим».

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

SP·09

Шаг за шагом

  1. 01

    Начните с машины, которая уже защищена

    Резолвер — маленький, тихий сервис, и именно поэтому так и тянет поставить его на что попало. Не надо — эта машина увидит каждое имя, которое ищет каждое устройство в вашем туннеле, так что она заслуживает того же обращения, что и всё остальное, хранящее секреты. Разверните самый маленький тариф, какой нравится; 1 ГБ памяти более чем достаточно для одного домохозяйства, поскольку размеры кэша ниже измеряются десятками мегабайт. Пройдите первый час после развёртывания прежде чем делать что-либо ещё: именованный пользователь, SSH только по ключу, default-deny на обоих семействах IP, автоматические обновления безопасности.

    Затем установите Unbound. Пакет дистрибутива поставляется с корневыми hints и корневым доверенным якорем DNSSEC и настраивает автоматическую ротацию этого якоря — один из немногих случаев, когда версия из пакетного менеджера по-настоящему избавляет вас от целого класса будущих поломок.

    sudo apt update && sudo apt install -y unbound dnsutils
    unbound -V | head -n 3
    
    # the packaged trust anchor the resolver will validate against
    sudo ls -l /var/lib/unbound/root.key

    Если root.key отсутствует, значит ваш пакет не запустил unbound-anchor, и валидация будет отказывать закрыто вообще на всём. Сгенерируйте его один раз командой sudo -u unbound unbound-anchor -a /var/lib/unbound/root.key, прежде чем продолжать.

  2. 02

    Верните порт 53 у systemd-resolved

    На большинстве современных дистрибутивов порт 53 уже кем-то занят: systemd-resolved держит заглушку-слушатель на 127.0.0.53, а /etc/resolv.conf — это симлинк, указывающий на неё. Unbound откажется стартовать — либо стартует, но не забиндит ничего полезного, — пока это не будет решено. Сначала посмотрите, потом редактируйте.

    ss -ulpn 'sport = :53'
    ls -l /etc/resolv.conf

    Прочтите этот абзац, прежде чем выполнять следующий блок: в промежутке между отключением заглушки и запуском Unbound на этой машине вообще не работает DNS. Закончите шаги 3 и 4 за один присест и не запускайте операцию apt в другом окне заранее — она зависнет на резолвинге имени, а вы примете это за резолвер, который вы ещё не запустили.

    # stop resolved from holding the port (appends inside the [Resolve] section)
    printf 'DNSStubListener=no\n' | sudo tee -a /etc/systemd/resolved.conf
    sudo systemctl restart systemd-resolved
    
    # point the host at the resolver it is about to run
    sudo rm -f /etc/resolv.conf
    printf 'nameserver 127.0.0.1\noptions edns0 trust-ad\n' | sudo tee /etc/resolv.conf
    
    # the port should now be free
    ss -ulpn 'sport = :53'
  3. 03

    Напишите конфигурацию, которая рекурсит и забывает

    Оставьте пакетную конфигурацию в покое и добавьте свой собственный файл в drop-in-каталоге, чтобы обновление пакета никогда молча не отменяло ваши решения. Каждая строка ниже — либо про отказ незнакомцам, либо про рекурсию от корня, либо про то, чтобы не оставлять записей. Замените 10.66.0.1 и 10.66.0.0/24 на адрес туннеля и подсеть с вашего собственного сервера WireGuard, если они отличаются.

    sudo tee /etc/unbound/unbound.conf.d/private-resolver.conf > /dev/null <<'EOF'
    server:
        # listen for the host itself and for the tunnel — never on 0.0.0.0
        interface: 127.0.0.1
        interface: 10.66.0.1
        port: 53
    
        # default-deny: refuse the internet, then allow what you trust
        access-control: 0.0.0.0/0 refuse
        access-control: ::/0 refuse
        access-control: 127.0.0.0/8 allow
        access-control: 10.66.0.0/24 allow
    
        # recurse from the root and send each server only the label it needs
        qname-minimisation: yes
        harden-dnssec-stripped: yes
        harden-below-nxdomain: yes
        harden-glue: yes
        aggressive-nsec: yes
        use-caps-for-id: yes
    
        # answer nothing about the software or the host
        hide-identity: yes
        hide-version: yes
    
        # keep no query log, and say little to the journal
        verbosity: 0
        log-queries: no
        log-replies: no
    
        # a warm cache is an upstream observation that never happens
        cache-min-ttl: 120
        cache-max-ttl: 86400
        prefetch: yes
        prefetch-key: yes
        serve-expired: yes
    
        # belt and braces if a rule above is ever loosened
        ratelimit: 1000
        ip-ratelimit: 100
    
        # sizing for a small instance
        num-threads: 2
        so-reuseport: yes
        msg-cache-size: 32m
        rrset-cache-size: 64m
    EOF

    Обратите внимание, чего нет в этом файле: здесь нет forward-zone. Именно его отсутствие делает это рекурсивным резолвером, а не кэшем перед чужим. Если позже вы вставите сюда сниппет из какого-нибудь туториала, который добавляет такую секцию, вы тихо перечеркнёте весь смысл этого упражнения.

  4. 04

    Запустите и докажите, что DNSSEC действительно валидирует

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

    sudo unbound-checkconf
    sudo systemctl enable --now unbound
    systemctl --no-pager status unbound | head -n 5

    Теперь проверьте два поведения, которые действительно важны, потому что резолвер, который отвечает, — это не то же самое, что резолвер, который валидирует. Подписанное имя обязано вернуться с флагом ad — authenticated data, — а имя с намеренно сломанными подписями обязано отказать закрыто с SERVFAIL, а не зарезолвиться.

    # should show: flags: qr rd ra ad
    dig @127.0.0.1 example.com +dnssec | grep -E '^;; flags'
    
    # should show: status: SERVFAIL  (not NOERROR, not an address)
    dig @127.0.0.1 dnssec-failed.org | grep -E '^;; ->>HEADER'
    
    # and a normal name should simply work
    dig @127.0.0.1 +short cloudflare.com

    Если сломанная зона резолвится в адрес, значит валидация не работает: проверьте, что /var/lib/unbound/root.key существует и доступен для чтения пользователю unbound. Если всё подряд отдаёт SERVFAIL, обычная причина — это сильно разъехавшиеся часы: у подписей есть окна действительности, и машина, разошедшаяся на несколько часов, отказывает всему интернету сразу.

  5. 05

    Закройте публичную дверь, оставьте открытым только туннель

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

    # DNS is reachable from the tunnel interface only
    sudo ufw allow in on wg0 to any port 53 proto udp
    sudo ufw allow in on wg0 to any port 53 proto tcp
    sudo ufw status verbose

    Затем проверьте это единственным способом, который что-то значит, — с другой машины. Проверка с самого сервера ничего не доказывает вообще — loopback разрешён намеренно. Запустите это с вашего ноутбука с отключённым туннелем, или с любого другого хоста, который у вас есть.

    # must time out. an answer here means you are running an open resolver.
    dig @SERVER_PUBLIC_IP example.com +time=3 +tries=1
    
    # same question over IPv6, which is the half people forget
    dig -6 @SERVER_IPV6 example.com +time=3 +tries=1

    Если хоть один из них вернул ответ, остановитесь и исправьте это прямо сейчас, а не после жалобы на абьюз. Обычные причины — это оставшийся в пакетном конфиге interface: 0.0.0.0, правило firewall, разрешающее 53 глобально после более раннего эксперимента, либо Docker, публикующий порт контейнера в обход ufw целиком.

  6. 06

    Направьте на него ваши устройства

    На стороне клиента это правка в одну строку. В конфигурации клиента WireGuard строка DNS становится адресом туннеля вашего сервера вместо публичного резолвера — это и есть та правка, что отправляет на пенсию DNS = 9.9.9.9 из руководства по WireGuard.

    [Interface]
    PrivateKey = <paste client.key>
    Address = 10.66.0.2/32, fd86:ea04:1115::2/128
    DNS = 10.66.0.1
    
    [Peer]
    PublicKey = <paste server.pub>
    Endpoint = YOUR_SERVER_IP:51820
    AllowedIPs = 0.0.0.0/0, ::/0
    PersistentKeepalive = 25

    Поднимите туннель и подтвердите с клиента две отдельные вещи: что ответы приходят от вашего резолвера, и что рекурсия на самом деле выходит в мир именно с вашего VPS, а не откуда-то ещё. Вторая проверка — та, что реально полезна: whoami.akamai.net возвращает адрес того резолвера, который спросил, так что он должен напечатать публичный IP вашего сервера и ничего больше.

    # answers should come from the tunnel address
    dig example.com | grep -E 'SERVER:'
    
    # should print your VPS public IP — this is the leak test
    dig +short whoami.akamai.net
    
    # and the ad flag should still be there, end to end
    dig example.com +dnssec | grep -E '^;; flags'
  7. 07

    Добавьте DNS-over-TLS только для устройства, которое не может держать туннель

    Пропустите этот шаг, если у вас нет конкретного устройства — как правило, это телефон на Android с его системной настройкой Private DNS, — которое вы хотите покрыть без постоянного туннеля. Для этого нужны имя хоста и сертификат, а это имя хоста становится постоянной публичной записью в логах Certificate Transparency, так что выбирайте такое, которое никак не ведёт к вашим прочим личностям.

    sudo apt install -y certbot
    sudo ufw allow 80/tcp comment 'certbot, temporarily'
    sudo certbot certonly --standalone -d dns.example.net
    sudo ufw delete allow 80/tcp
    
    # unbound must be able to read the key
    sudo usermod -a -G ssl-cert unbound 2>/dev/null || true
    sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
    sudo chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive

    Добавьте TLS-листенер отдельным drop-in-файлом, чтобы можно было удалить его одним движением, если вы измените решение.

    # NOTE: drop-in files are read in alphabetical order and the last
    # access-control line for a given prefix wins — so this file must
    # sort AFTER private-resolver.conf, or its refuse rule overrides
    # the allow below and every DoT client gets REFUSED.
    sudo tee /etc/unbound/unbound.conf.d/zz-dot.conf > /dev/null <<'EOF'
    server:
        interface: 0.0.0.0@853
        interface: ::0@853
        tls-service-key: "/etc/letsencrypt/live/dns.example.net/privkey.pem"
        tls-service-pem: "/etc/letsencrypt/live/dns.example.net/fullchain.pem"
        # roaming clients have no fixed address, so this endpoint must accept any.
        # safe only because port 53 stays bound to loopback + wg0 and blocked at
        # the firewall — TLS over TCP cannot be spoofed, so it cannot be reflected.
        access-control: 0.0.0.0/0 allow
        access-control: ::/0 allow
    EOF
    sudo ufw allow 853/tcp
    sudo unbound-checkconf && sudo systemctl restart unbound

    Проверьте это снаружи туннеля, прежде чем довериться, а затем впишите имя хоста в поле Private DNS на телефоне. Продление — это та часть, которая тихо ломается три месяца спустя: certbot заменяет сертификат, но Unbound держит старый в памяти, так что добавьте deploy-хук, который его перезагружает.

    kdig -d @dns.example.net +tls-ca +tls-host=dns.example.net example.com
    
    echo 'systemctl reload unbound' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/unbound.sh
    sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/unbound.sh
SP·10 — FAQ

Быстрые ответы

Мой собственный резолвер быстрее или медленнее, чем 1.1.1.1?

И то, и то — в разные моменты. Холодный запрос к домену, который никто в вашей сети ещё не посещал, медленнее, потому что полная рекурсия — это запрос к корню, затем к TLD, затем к авторитативному серверу, тогда как публичный резолвер обычно отвечает из кэша, прогретого миллионами других людей. Тёплый запрос быстрее, чем способен быть любой публичный резолвер, потому что кэш находится на другом конце вашего собственного туннеля, а не где-то через весь интернет. В повседневном использовании доминирует именно тёплый случай: домохозяйства повторно заходят на несколько сотен доменов, prefetch обновляет популярные из них до того, как они истекут, и процент попаданий в кэш растёт уже в течение первого дня. Если то, что первый визит на незнакомый сайт чуть медленнее, важнее для вас, чем сам лог, — это неправильный обмен, и вам стоит настроить форвардинг вместо этого.

Мешает ли это моему провайдеру видеть, какие сайты я посещаю?

Само по себе — нет. Оно мешает вашему провайдеру читать ваши запросы только если эти запросы идут внутри туннеля — иначе ваши запросы к VPS всё равно летят открытым UDP-текстом по сети вашего провайдера, и вы просто сменили адресата, ничего не скрыв. Даже с туннелем ваш провайдер всё равно видит зашифрованную сессию к одному адресу, а если вы бродите по сети вне туннеля, он всё равно видит IP-адреса назначения и, в отсутствие Encrypted Client Hello, имя сервера в TLS-хендшейке. Считайте приватный резолвер компонентом, который закрывает канал имён, а туннель — компонентом, который закрывает транспортный канал. Один не заменяет другой.

Не проще ли просто форвардить на Quad9 или Cloudflare через DoT?

Это законный выбор, и для многих людей — правильный. Форвардинг через DoT проще, быстрее настраивается, даёт вам анонимность очень большой толпы и побеждает того наблюдателя, с которым большинство людей реально сталкивается, — локальную сеть и провайдера. Чего он не делает — так это не убирает агрегацию: одна организация всё равно получает весь ваш поток запросов, теперь аккуратно подписанный единственным исходным адресом. Выбирайте форвардинг, если ваша модель угроз — это кафе с Wi-Fi и ваш провайдер; выбирайте рекурсию, если вас не устраивает сама агрегация как таковая, или если вы не хотите каждый раз решать, чьей политике хранения данных верить.

Может ли мой хостинг-провайдер читать мои DNS-запросы?

Да, и на это стоит смотреть без иллюзий. Рекурсивные запросы к корневым, TLD- и авторитативным серверам покидают VPS по UDP-порту 53 без всякого шифрования — до корневых серверов никакого DoT не существует. Прочитать их может любой, у кого есть видимость аплинка вашего сервера. QNAME-минимизация означает, что каждый отдельный разговор раскрывает только фрагмент, и эти фрагменты перемешаны со всем остальным, что делает эта машина, но скрытыми они не являются. Вы всего лишь переместили наблюдателя от розничного провайдера в вашей собственной стране, который обязан хранить данные и коммерчески заинтересован в них, к хостинг-сети, которую вы сами выбрали. Это делает вопрос вопросом о том, какая юрисдикция и какой провайдер, а не вопросом, на который отвечает конфигурация.

Нужно ли мне для этого доменное имя?

Нет, если ваши устройства достигают резолвера через туннель, а это и есть главная практическая причина предпочесть этот путь: нет имени хоста, нет сертификата, нет публичной записи о том, что сервис вообще существует. Домен нужен только для DNS-over-TLS, потому что клиенты проверяют сертификат по имени. Учтите, что получение этого сертификата навечно публикует имя хоста в логах Certificate Transparency, а passive DNS вскоре после этого свяжет его с адресом сервера, — так что не используйте поддомен домена, который уже указывает на вас. Анонимная регистрация домена разбирает, что всё равно утекает, когда вы пытаетесь это сделать.

Заблокирует ли это рекламу и трекеры?

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

Применить на практике

VPS в онлайне за 15 min, выделенный передаётся за 2–12 h. Пополните от $30.00 в крипте — без привязки личности.

Развернуть VPS