Знакомая ситуация. Сервер только что отвечал на пинги, обновлялся без вопросов, а через час ни одно имя не резолвится. Браузер на десктопе говорит, что сайт не найден, apt не может достучаться до репозиториев, а команда host при этом упрямо показывает правильный адрес. Парадокс выглядит необъяснимым ровно до того момента, пока не вспоминаешь, что на современном Linux именем занимается не один механизм, а как минимум два, и они не всегда согласны друг с другом.

В центре этой истории стоит systemd-resolved. Сервис задумывался как удобная прослойка между приложениями и реальными DNS-серверами, с кешем, поддержкой разных серверов на разных интерфейсах и валидацией DNSSEC. На бумаге всё стройно. На практике он соседствует со старым добрым файлом resolv.conf, к которому привыкли поколения администраторов, и вот на этом стыке рождается большинство ночных разборов. Стоит понять механику конфликта, и загадочные сбои превращаются в предсказуемую инженерную задачу с понятным решением.

Откуда вообще взялся конфликт и почему host работает, а программа нет

Исторически резолвинг в Linux был устроен примитивно и понятно. Существовал файл со списком серверов имён, библиотека читала его сверху вниз и спрашивала первый доступный сервер. Администратор правил файл руками и шёл дальше заниматься делами. Проблема вылезла, когда среды стали динамическими. Ноутбуки переключаются между сетями, облачные машины получают адреса по DHCP, туннельные подключения поднимают свои маршруты и хотят свои серверы имён. Статический файл перестал успевать за реальностью, и появились инструменты, которые переписывали его на лету по событиям сети.

systemd-resolved пошёл дальше и добавил собственный слой. Он поднимает локальный заглушечный резолвер на адресе 127.0.0.53 и порту 53, и идея в том, чтобы все приложения спрашивали именно его, а он уже принимал умные решения, куда переслать запрос дальше. Локальный заглушечный слушатель работает на 127.0.0.53 и используется приложениями, которые обрабатывают DNS-запросы напрямую. Здесь и зарыта первая мина. Часть программ ходит через системную библиотеку glibc и слушается настроек в nsswitch, а часть лезет напрямую в resolv.conf или вообще говорит с резолвером по своему протоколу. Если эти пути настроены по-разному, одни и те же имена начинают резолвиться по-разному.

Классический симптом выглядит именно так. Команда host отвечает мгновенно и корректно, потому что она обращается к серверу напрямую в обход системного слоя. А реальное приложение, тот же менеджер ключей или apt, идёт через resolv.conf к заглушке и получает отказ. Запросы приложения уходят на 127.0.0.53 через resolv.conf, тогда как host обходит systemd-resolved полностью. Вывод из этого один. Если host работает, а программа нет, проблема не в самом DNS-сервере, а в том, каким маршрутом до него идёт конкретный софт.

Что показывает resolv.conf и почему символическая ссылка решает всё

Первое, что нужно выяснить при разборе, это куда вообще указывает главный файл конфигурации резолвинга. От его содержимого зависит, попадёт приложение в заглушку systemd-resolved или пойдёт мимо неё.

# Смотрим, что собой представляет /etc/resolv.conf
ls -la /etc/resolv.conf

# И что внутри
cat /etc/resolv.conf

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

# Ожидаемое содержимое при режиме stub
nameserver 127.0.0.53
options edns0 trust-ad
search .

Тонкость в том, что у systemd-resolved есть два разных режима, и они дают похожие на вид, но принципиально разные файлы. Режим stub направляет клиентов на заглушку 127.0.0.53, и тогда все запросы проходят через логику сервиса с кешем и пересылкой. Файл stub-resolv.conf указывает клиентским библиотекам отправлять запросы на 127.0.0.53, где systemd-resolved слушает и затем пересылает их дальше. Второй режим отдаёт клиентам напрямую те адреса серверов, которые сервис вычислил из конфигурации и DHCP. Этот вариант обходит шаг пересылки systemd-resolved, но вместе с ним обходит и всю его логику принятия решений о том, куда именно пересылать каждый запрос. Понимание, в каком из режимов живёт система, наполовину решает задачу диагностики.

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

# Бэкап на всякий случай
sudo cp /etc/resolv.conf /etc/resolv.conf.backup

# Возвращаем правильную символическую ссылку
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

Диагностика через resolvectl, или как посмотреть в голову резолверу

Прежде чем что-то менять, стоит выяснить, что сервис вообще думает о текущей конфигурации. Основной инструмент здесь это resolvectl, который показывает картину по каждому сетевому интерфейсу отдельно.

# Общий статус резолвинга
resolvectl status

# Подробно по всем интерфейсам
resolvectl status --all

# Какие серверы и домены используются
resolvectl dns
resolvectl domain

В выводе важны несколько строк. Поле с режимом resolv.conf прямо говорит, в каком из двух состояний система. Текущий сервер показывает, куда реально уходят запросы после прохождения заглушки. Состояние DNSSEC намекает на потенциальный источник загадочных отказов, о котором речь ниже. По каждому интерфейсу видно свой набор серверов, и именно здесь обнаруживается, что туннельное подключение или второй сетевой адаптер тянет резолвинг в свою сторону.

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

# Запрос через системный механизм
resolvectl query example.com

# Запрос напрямую к заглушке
dig @127.0.0.53 example.com

# Запрос мимо заглушки, к внешнему серверу
dig @1.1.1.1 example.com

Расхождение в ответах этих трёх команд это и есть диагноз. Когда внешний сервер отвечает, а заглушка молчит или возвращает ошибку, проблема внутри systemd-resolved. Когда заглушка отвечает, а приложение всё равно не видит имён, копать нужно в сторону того, каким механизмом резолвинга пользуется конкретная программа.

Битва за порт 53, главный источник отказов на серверах

Самая частая причина, по которой systemd-resolved ломает резолвинг на сервере, это конфликт за порт. Заглушка хочет занять адрес 127.0.0.53 и порт 53, но если на машине стоит свой кеширующий резолвер вроде dnsmasq или bind, он тоже метит в порт 53. Два процесса не могут слушать один порт, один из них падает или не стартует, и резолвинг рассыпается. Поскольку обе службы пытаются занять порт 53, они конфликтуют, из-за чего разрешение имён перестаёт работать.

Проверяется это одной командой, которая показывает, кто именно держит порт.

# Кто слушает порт 53
sudo ss -ltnup | grep ':53'

# Альтернатива через netstat
sudo netstat -tulpn | grep ':53'

Типичная картина конфликта выглядит так. Один процесс сидит на 127.0.0.1 порт 53, другой на 127.0.0.53 порт 53, и приложения, идущие через resolv.conf, попадают не туда, куда ожидают.

tcp  0  0  127.0.0.1:53    0.0.0.0:*  LISTEN  1234/dnsmasq
tcp  0  0  127.0.0.53:53   0.0.0.0:*  LISTEN  5678/systemd-resolv

Решений тут два, и выбор зависит от того, кто на машине главный по DNS. Если основным резолвером должен быть dnsmasq или bind, заглушку systemd-resolved нужно отключить, освободив порт. Делается это правкой конфигурации сервиса.

sudo nano /etc/systemd/resolved.conf

В секции Resolve выставляется отключение заглушки. Полезно сразу задать и серверы по умолчанию, чтобы сам сервис продолжал работать как клиент.

[Resolve]
DNS=1.1.1.1 8.8.8.8
DNSStubListener=no

После правки сервис перезапускается и проверяется, что порт освободился.

sudo systemctl restart systemd-resolved
sudo ss -ltnup | grep ':53'

Отключение заглушечного слушателя освобождает порт 53 для dnsmasq. Стоит учесть версию systemd, потому что опция отключения заглушки появилась не сразу. Версия 247 поддерживает опцию DNSStubListener no, а на версиях старше 232 приходится полагаться на другие методы вроде правки зависимостей служб.

Коварная история с DNSSEC, когда ломается ровно одна программа

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

Корень в том, что при определённых условиях systemd-resolved не умеет корректно отдавать некоторые типы записей и возвращает ошибку формата вместо нормального ответа. Записи RRSIG содержат цифровые подписи DNSSEC, и при определённых условиях systemd-resolved не поддерживает запросы этих записей. Программа, которая ждёт такую запись, получает формат-эррор, трактует его как фатальный сбой и отказывается работать, хотя само имя прекрасно резолвится через любой внешний сервер.

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

# Запрос служебной записи через заглушку
host -t RRSIG example.com 127.0.0.53

# Тот же запрос напрямую к внешнему серверу
host -t RRSIG example.com 1.1.1.1

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

[Resolve]
DNSSEC=no

Когда проще уйти на классическую схему, чем чинить надстройку

Иногда вывод по итогам диагностики неутешительный. На конкретной машине systemd-resolved приносит больше боли, чем пользы. Это типично для серверов под контейнерные платформы, для нод оркестраторов, для систем с несколькими туннельными подключениями и нестандартной маршрутизацией, где умная логика сервиса воюет с не менее умной логикой других компонентов. В таких случаях разумнее не латать надстройку, а вернуться к прямой и предсказуемой классической схеме, когда resolv.conf это обычный файл со списком серверов, и точка.

Переход делается аккуратно и в определённом порядке, иначе можно остаться вообще без резолвинга. Сначала сервис останавливается и снимается с автозапуска.

sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo systemctl mask systemd-resolved

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

# Удаляем симлинк на динамический файл заглушки
sudo rm /etc/resolv.conf

# Создаём обычный статический файл
sudo tee /etc/resolv.conf > /dev/null << 'EOF'
nameserver 1.1.1.1
nameserver 8.8.8.8
options timeout:2 attempts:3
EOF

Остаётся одна засада, о которой забывают чаще всего. Менеджер сети при следующем событии может снова переписать файл по-своему. Чтобы этого не происходило, нужно либо настроить менеджер сети так, чтобы он сам управлял файлом по классической схеме, либо защитить файл от перезаписи. На системах с NetworkManager управление резолвингом переключается в его конфигурации.

# /etc/NetworkManager/conf.d/dns.conf
[main]
dns=default

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

sudo systemctl restart NetworkManager

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

# Запрещаем любые изменения файла
sudo chattr +i /etc/resolv.conf

# Когда понадобится снова его править, атрибут снимается так
sudo chattr -i /etc/resolv.conf

Важная деталь для тех, кто использует resolvconf или openresolv для динамического управления файлом. После отказа от systemd-resolved нельзя оставлять в системе пакет-прослойку, который притворяется рабочим резолвером. Если systemd-resolved не используется, пакет systemd-resolvconf должен быть удалён, иначе он вызовет проблемы у сетевого софта, который ожидает рабочий бинарник resolvconf. Логика простая. Один менеджер файла, одна точка истины, никаких посредников, которые молча перехватывают управление.

Как закрепить результат и не вернуться к тому же сбою через месяц

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

# После reboot прогоняем контрольный набор
ls -la /etc/resolv.conf
sudo ss -ltnup | grep ':53'
resolvectl status 2>/dev/null || echo "resolved отключён, это ожидаемо"

# Финальная проверка реального резолвинга
getent hosts example.com
curl -sI https://example.com | head -n 1

Команда getent здесь принципиальна. В отличие от dig и host, она ходит ровно тем же путём через системную библиотеку, что и большинство реальных приложений, поэтому её ответ это честная имитация того, что увидит apt, curl или менеджер пакетов. Если getent резолвит имя, значит, проблема действительно закрыта, а не замаскирована.

Главный вывод из всей этой истории не про конкретные команды. Он про подход. Сбои резолвинга на современном Linux почти никогда не означают, что сломан сам DNS. В подавляющем большинстве случаев сломан маршрут, которым конкретный софт идёт до сервера имён, и виновата рассогласованность между умной надстройкой и привычным файлом. Стоит держать в голове карту этих маршрутов, кто слушает порт, куда указывает resolv.conf, в каком режиме живёт сервис, каким механизмом пользуется приложение, и непонятные ночные сбои превращаются в короткую цепочку проверок с предсказуемым финалом. А там, где надстройка стабильно мешает, классическая схема с обычным файлом по-прежнему остаётся честным и работающим выбором, не уступающим современным решениям ни в чём, кроме модности.