DNS nie działa przez WireGuard: jak naprawić błędy
Tunel WireGuard jest aktywny, ale nazwy nie są rozwiązywane lub zapytania wyciekają poza sieć VPN. Zidentyfikuj jeden z trzech typów awarii DNS i zastosuj poprawne rozwiązanie.
Dlaczego DNS przestaje działać w momencie nawiązania tunelu WireGuard
DNS przez WireGuard zawodzi na trzy sposoby, a każdy z nich wymaga innego rozwiązania. Rozwiązywanie nazw nie działa w ogóle, zapytania opuszczają maszynę poza tunelem lub menedżer usług DNS klienta nadpisuje ustawienia kilka sekund po uruchomieniu interfejsu. Problem niemal nigdy nie leży w samym tunelu. Przyczyną jest linia wskazująca klientowi, z jakiego serwera DNS korzystać, oraz routing decydujący o tym, jak pakiety do tego serwera są przesyłane.
WireGuard przesyła pakiety IP i nie posiada wiedzy o DNS (systemie nazw domenowych, usłudze zamieniającej nazwy takie jak example.com na adresy IP). Linia DNS = w bloku [Interface] klienta nie jest ustawieniem WireGuard. Jest ona odczytywana przez wg-quick, skrypt powłoki inicjujący interfejs, a następnie wg-quick modyfikuje konfigurację resolvera klienta podczas działania tunelu i przywraca ją przy wg-quick down. Każdy z poniższych problemów jest zatem problemem routingu lub wg-quick, a nigdy problemem kryptograficznym. Jeśli tunel nie został jeszcze zestawiony, należy zacząć od własnego serwera VPN WireGuard na VPS i wrócić do tej strony później.
Przed przystąpieniem do konfiguracji DNS należy upewnić się, że tunel działa poprawnie.
sudo wg show
ping -c 3 10.8.0.1
ping -c 3 1.1.1.1wg show powinno wyświetlić peer z niedawnym latest handshake, a oba polecenia ping powinny otrzymywać odpowiedzi. Jeśli ping 1.1.1.1 kończy się przekroczeniem czasu oczekiwania, problem najprawdopodobniej dotyczy przekazywania pakietów lub NAT (network address translation), a nie DNS; żadna zmiana konfiguracji resolvera w takim przypadku nie pomoże. Firewall odrzucający pakiety ICMP powoduje ten sam błąd, dlatego przed zmianą reguł przekazywania należy zweryfikować stan za pomocą curl -sI https://1.1.1.1. Jeśli pingi odpowiadają, ale przepustowość spada po rozpoczęciu przesyłania rzeczywistego ruchu, jest to odrębny problem, a niska wydajność WireGuard niemal zawsze wynika z MTU, a nie z kwestii opisanych na tej stronie. Każdy przykład tutaj używa 10.8.0.0/24 jako podsieci tunelu oraz 10.8.0.1 jako adresu tunelu serwera. Należy podstawić własne wartości.
Pierwsza awaria: brak rozwiązywania nazw, ponieważ resolver nie odpowiada
Objaw jest precyzyjny. ping 1.1.1.1 działa, a curl https://example.com zwraca następujący wynik:
curl: (6) Could not resolve host: example.comNależy odpytać resolver tunelu bezpośrednio z poziomu klienta. Narzędzie dig pochodzi z pakietu dnsutils w systemach Ubuntu i Debian.
dig +short @1.1.1.1 example.com
dig +short @10.8.0.1 example.comPierwsze polecenie zwraca adres, co dowodzi, że pakiety docierają do Internetu przez tunel. Drugie nie zwraca nic i wyświetla ;; communication timed out; no servers could be reached. To cała diagnoza: klient jest skierowany na 10.8.0.1, a 10.8.0.1 nie odpowiada na porcie UDP 53.
Przyczyną są dwa stany. Albo na serwerze nie działa żaden resolver, albo firewall serwera odrzuca zapytanie, zanim dotrze ono do celu. Należy sprawdzić oba te aspekty na serwerze.
sudo ss -ulnp | grep ':53'
sudo nft list rulesetDziałający i poprawnie powiązany resolver wyświetla linię z 10.8.0.1:53 lub 0.0.0.0:53. W systemie Ubuntu niespodzianką jest zazwyczaj 127.0.0.53:53: to nasłuchujący stub systemd-resolved, który wiąże się z adresem pętli zwrotnej i jest celowo nieosiągalny z innych maszyn. Skierowanie klienta VPN na serwer, którego jedynym resolverem jest ten stub, powoduje dokładnie taki przekroczenie czasu oczekiwania.
Rozwiązaniem jest resolver nasłuchujący na adresie tunelu oraz reguła firewalla zezwalająca partnerom na dostęp do niego.
sudo apt update && sudo apt install -y unbound
printf 'server:\n interface: 10.8.0.1\n access-control: 10.8.0.0/24 allow\n' \
| sudo tee /etc/unbound/unbound.conf.d/wireguard.conf
sudo systemctl restart unbound
sudo ss -ulnp | grep '10.8.0.1:53'Następnie należy otworzyć port wyłącznie dla ruchu tunelowego. W przypadku nftables należy dodać te dwie linie do łańcucha input w pliku /etc/nftables.conf i przeładować konfigurację za pomocą sudo systemctl reload nftables.
udp dport 53 iifname "wg0" accept
tcp dport 53 iifname "wg0" acceptW przypadku ufw, polecenie sudo ufw allow in on wg0 to any port 53 wykonuje to samo zadanie. Nigdy nie należy otwierać portu 53 dla publicznego Internetu. Otwarty resolver rekurencyjny zostanie wykryty przez skanery w ciągu kilku dni i wykorzystany do wzmacniania ataków typu denial of service, a dostawca usług zauważy ten ruch szybciej niż administrator.
Należy ponownie uruchomić dig +short @10.8.0.1 example.com z poziomu klienta. Adres w wyniku oznacza, że ścieżka do resolvera działa, więc klient musi go teraz tylko użyć. Należy dodać odpowiednią linię do bloku [Interface] klienta i zrestartować interfejs za pomocą sudo wg-quick down wg0 && sudo wg-quick up wg0.
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1Awaria druga: wycieki DNS, ponieważ split tunnel nie kieruje ruchu do resolvera
Ten problem jest poważniejszy, ponieważ wszystko wydaje się działać poprawnie. Nazwy są rozwiązywane, strony się ładują, a zapytania przesyłane są otwartym tekstem przez sieć lokalną, której użytkownik nie chciał obdarzyć zaufaniem.
Przyczyną są dwie konfiguracje. Pierwszą jest klient z AllowedIPs = 0.0.0.0/0, ::/0 bez linii DNS =. wg-quick instaluje domyślną trasę w swojej tablicy routingu i dodaje regułę z suppress_prefixlength 0, która celowo utrzymuje bardziej szczegółowe trasy lokalne, aby maszyna mogła nadal łączyć się z drukarką. Resolver, o którym klient dowiedział się przez DHCP, zazwyczaj router pod adresem 192.168.1.1, pasuje do jednej z tych tras lokalnych. Ruch przesyłany jest tunelem, ale sieć lokalna nadal otrzymuje pełną listę nazw, które są wyszukiwane.
Drugą przyczyną jest split tunnel: AllowedIPs = 10.8.0.0/24 z DNS = 9.9.9.9. Ponieważ 9.9.9.9 nie znajduje się wewnątrz AllowedIPs, klient nie ma do niego trasy przez tunel, więc zapytanie opuszcza maszynę przez łącze lokalne, dokładnie tak jak w pierwszym przypadku.
Należy sprawdzić, który resolver faktycznie odpowiada. whoami.akamai.net to publiczna nazwa testowa, która odpowiada adresem IP rekurencyjnego resolvera, który wysłał zapytanie, co pozwala porównać tę odpowiedź z publicznym adresem serwera.
resolvectl status
dig +short whoami.akamai.net
sudo tcpdump -ni any -c 10 port 53resolvectl status wyświetla jeden blok dla każdego łącza. Jeśli blok dla łącza ethernet lub bezprzewodowego nadal pokazuje Current DNS Server: 192.168.1.1, podczas gdy blok wg0 nie pokazuje nic, oznacza to wyciek. Wynik dig +short whoami.akamai.net zwracający domowy adres szerokopasmowy zamiast adresu serwera potwierdza to od strony zdalnej. Linia tcpdump stanowi ostateczny dowód: poprawna konfiguracja kieruje każdy pakiet na porcie 53 opuszczający maszynę na wg0, a wyciek kieruje je na wlan0 lub enp3s0. Pakiety na lo do lub z 127.0.0.53 to aplikacje komunikujące się z lokalnym stubem systemd-resolved i nie stanowią one wycieku.
Rozwiązanie składa się z dwóch części i obie są wymagane. Należy ustawić DNS na adres znajdujący się wewnątrz tunelu i upewnić się, że ten adres mieści się w AllowedIPs.
[Interface]
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 10.8.0.0/24, 10.20.0.0/1610.8.0.1 znajduje się wewnątrz 10.8.0.0/24, więc zapytanie jest szyfrowane i wysyłane do serwera. Jeśli konieczne jest użycie publicznego resolvera w ramach split tunnel, należy dodać go jako trasę hosta: AllowedIPs = 10.8.0.0/24, 9.9.9.9/32. Pakiety są wtedy przesyłane tunelem, choć sieć lokalna nadal może widzieć, że wybrano tego dostawcę na podstawie wcześniejszych sesji. Użycie własnego resolvera eliminuje ten problem.
Przypisanie resolvera jest jedną z widocznych różnic między ręcznie konfigurowanym WireGuard a skoordynowaną siecią mesh, co stanowi element kompromisu w porównaniu WireGuard z Tailscale. Uruchomienie własnego serwera kontrolnego Headscale zapewnia tę koordynację bez przekazywania materiałów kluczy stronie trzeciej. Jeśli to ostatnie stwierdzenie budzi obawy, należy zauważyć, że Tailscale nigdy nie przechowuje kluczy szyfrujących ruch, a istotniejszym pytaniem jest to, co skompromitowany serwer koordynacyjny lub skradzione konto tożsamości mogłyby wnieść do sieci.
Trzeci problem: konflikt resolvconf i systemd-resolved na klientach Linux
Klienci macOS, Windows, iOS oraz Android stosują DNS = za pośrednictwem oficjalnej aplikacji i rzadko sprawiają problemy. W systemie Linux ustawienie to jest wprowadzane przez skrypt powłoki, który musi odgadnąć, który z kilku dostępnych menedżerów rozpoznawania nazw jest używany.
Pierwsza awaria jest sygnalizowana wyraźnie. sudo wg-quick up wg0 zatrzymuje się z komunikatem:
resolvconf: command not foundwg-quick wywołuje resolvconf, a ten plik binarny nie jest zainstalowany. W systemach Debian 12 oraz Ubuntu 24.04 i nowszych, polecenie resolvconf, które komunikuje się z systemd-resolved, jest dostarczane w pakiecie systemd-resolved. Należy go zainstalować, a następnie ponownie uruchomić interfejs. Ubuntu 22.04 nie posiada osobnego pakietu dla tego narzędzia; w tym przypadku należy samodzielnie utworzyć dowiązanie symboliczne za pomocą sudo ln -s /usr/bin/resolvectl /usr/local/bin/resolvconf i pominąć instalację pakietu.
sudo apt install -y systemd-resolved
sudo systemctl restart systemd-resolved
sudo wg-quick down wg0 && sudo wg-quick up wg0
resolvectl status wg0Druga awaria jest cicha i to ona pochłania najwięcej czasu. Interfejs uruchamia się, resolvectl status wg0 poprawnie wskazuje DNS Servers: 10.8.0.1, a zapytania nadal trafiają do starego mechanizmu rozpoznawania nazw. systemd-resolved nie ignoruje wg0. Nazwa, która nie pasuje do żadnej domeny routingu, jest wysyłana do serwerów DNS każdego łącza, które jest traktowane jako domyślna trasa (default route). Łącze z serwerami DNS, ale bez zdefiniowanej domeny route-only, jest domyślnie traktowane jako takie łącze. W rezultacie zapytanie trafia jednocześnie do 10.8.0.1 oraz do routera w sieci bezprzewodowej, a wygrywa pierwsza otrzymana odpowiedź. Router znajduje się w odległości jednego przeskoku i zazwyczaj odpowiada pierwszy, a zapytanie w obu przypadkach opuszcza system w postaci jawnej. Nazwy wewnątrz domeny wyszukiwania łącza bezprzewodowego, takie jak lan, trafiają wyłącznie do tego łącza, ponieważ priorytet ma najbardziej szczegółowa domena routingu.
Gdy wg-quick stosuje DNS = za pośrednictwem własnego wrappera resolvconf w systemd, automatycznie żąda domeny routingu typu catch-all. Dlatego ten problem pojawia się głównie wtedy, gdy ustawienie zostało wprowadzone przez inną implementację resolvconf lub menedżer sieci. Jawne zdefiniowanie ustawienia eliminuje domysły.
Należy ustawić resolver i zadeklarować domenę routingu catch-all w tym samym kroku. %i rozwija się do nazwy interfejsu, więc ten blok działa bez zmian na dowolnym interfejsie.
[Interface]
Address = 10.8.0.2/32
PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~.
PostDown = resolvectl revert %iNależy usunąć linię DNS =, gdy używa się PostUp w ten sposób, ponieważ w przeciwnym razie dwa mechanizmy zapisują stan resolvera, a tylko jeden z nich czyści go po zakończeniu pracy. Argument ~. jest kluczowy: czyni on wg0 domeną route-only dla każdej nazwy, dzięki czemu systemd-resolved przestaje wysyłać do innych łączy nazwy, dla których nie znaleziono bardziej szczegółowej domeny. Nazwy wewnątrz domeny innego łącza, takie jak lan, nadal trafiają do tego łącza, co jest zazwyczaj pożądanym zachowaniem w przypadku hostów lokalnych. Należy to zweryfikować.
resolvectl status wg0Poprawny wynik zawiera DNS Servers: 10.8.0.1 oraz DNS Domain: ~.. Jeśli brakuje ~., oznacza to, że część resolvectl domain nie została wykonana, a wg0 ponownie rywalizuje z innymi łączami. Nie należy używać flagi trasy domyślnej jako wskaźnika poprawności: zawiera ona yes (lub +DefaultRoute w linii Protocols, w zależności od wersji systemd) nawet bez ~., ponieważ łącze bez domeny route-only jest domyślnie trasą domyślną.
Warto wspomnieć o jeszcze jednym przypadku. Jeśli /etc/resolv.conf jest rzeczywistym plikiem, a nie dowiązaniem symbolicznym do /run/systemd/resolve/stub-resolv.conf, oznacza to, że zarządza nim inny proces, zazwyczaj NetworkManager lub środowisko uruchomieniowe kontenerów. Przed rozpoczęciem debugowania czegokolwiek innego należy uruchomić ls -l /etc/resolv.conf, ponieważ narzędzie, które nadpisuje ten plik przy każdej zmianie sieci, cofnie wprowadzone zmiany w najmniej odpowiednim momencie.
Aktualizacja: własny serwer DNS z filtrowaniem przez tunel
Gdy zapytania są poprawnie przesyłane przez tunel, zdalny serwer DNS staje się punktem kontrolnym. Uruchomienie AdGuard Home w tej lokalizacji zapewnia filtrowanie z list blokad oraz dziennik zapytań dla każdego podłączonego urządzenia, bez konieczności instalacji oprogramowania klienckiego czy konfiguracji poszczególnych urządzeń. Oficjalny skrypt instalacyjny, zweryfikowany w październiku 2026, składa się z jednej linii.
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -vKreator konfiguracji podczas pierwszego uruchomienia nasłuchuje na porcie 3000. Należy uzyskać do niego dostęp przez tunel pod adresem http://10.8.0.1:3000, zamiast otwierać ten port publicznie. W kreatorze należy ustawić adres nasłuchiwania DNS oraz adres panelu administracyjnego na 10.8.0.1. Jeśli unbound z pierwszego punktu awarii nadal korzysta z tego samego adresu, należy go najpierw zatrzymać za pomocą sudo systemctl disable --now unbound, ponieważ oba procesy będą rywalizować o port UDP 53 na tym samym adresie, co spowoduje zakończenie działania AdGuard Home z błędem listen udp 10.8.0.1:53: bind: address already in use.
Konfiguracje klientów nie wymagają zmian, jeśli wskazują już na DNS = 10.8.0.1. Dziennik zapytań będzie teraz rejestrował każde wyszukiwanie pochodzące od każdego węzła, chyba że wykluczysz klienta z logowania. Jest to realna decyzja dotycząca prywatności, a nie darmowy zysk: przenosisz zaufanie z dostawcy internetu na siebie i to Ty musisz dbać o aktualizacje zabezpieczeń tego serwera. Serwer wystawiony na działanie internetu wymaga najpierw wdrożenia podstawowych zabezpieczeń, które opisano w pierwsze dziesięć minut na nowym VPS.
FAQ
Dlaczego tunel WireGuard łączy się, ale nazwy nie są rozwiązywane?
Tunel przesyła pakiety i nie obsługuje nazw, więc działający tunel przy niedziałającym rozwiązywaniu nazw oznacza, że wskazany resolver nie odpowiada. Należy przeprowadzić test za pomocą dig +short @10.8.0.1 example.com z poziomu klienta. Odpowiedź communication timed out oznacza, że albo żaden resolver nie nasłuchuje na tym adresie tunelu (często dlatego, że stub systemd-resolved jest powiązany tylko z 127.0.0.53), albo firewall serwera odrzuca ruch UDP na porcie 53 przychodzący na wg0. Należy najpierw naprawić usługę nasłuchującą, a następnie otworzyć port wyłącznie dla wg0.
Jak sprawdzić, czy DNS wycieka poza WireGuard?
Należy uruchomić sudo tcpdump -ni any -c 10 port 53 na kliencie i obserwować kolumnę interfejsu podczas przeglądania sieci. Każdy pakiet opuszczający maszynę powinien znajdować się na wg0; pakiety na lo do lub z 127.0.0.53 to lokalny stub systemd-resolved i jest to poprawne zachowanie. Jeśli pakiety pojawiają się na interfejsie bezprzewodowym lub ethernet, zapytania są wysyłane otwartym tekstem. dig +short whoami.akamai.net pozwala uzyskać drugą opinię, ponieważ odpowiada adresem publicznym resolvera rekurencyjnego, który wysłał zapytanie; odpowiedź inna niż adres serwera potwierdza wyciek.
Czy linia DNS = jest potrzebna w przypadku split tunnel?
Tak, a adres resolvera musi również znajdować się wewnątrz AllowedIPs, w przeciwnym razie klient nie będzie miał do niego trasy. Przy AllowedIPs = 10.8.0.0/24 resolver pod adresem 10.8.0.1 jest objęty tunelem, a zapytanie jest szyfrowane. Publiczny resolver, taki jak 9.9.9.9, nie jest objęty tunelem, więc zapytanie wychodzi przez łącze lokalne, nawet jeśli linia DNS wygląda na poprawną.
Dlaczego resolvectl pokazuje właściwy serwer, ale zapytania trafiają gdzie indziej?
systemd-resolved wysyła nazwę, która nie pasuje do żadnej domeny routingu, do wszystkich łączy z domyślną trasą jednocześnie, w tym wg0, i zwraca pierwszą poprawną odpowiedź, która zazwyczaj pochodzi z lokalnego routera. Należy dodać PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~. do bloku [Interface] klienta i usunąć linię DNS =. Wtedy resolvectl status wg0 powinno wskazywać DNS Domain: ~., a każda nazwa, której nie przypisano do bardziej szczegółowej domeny, będzie kierowana wyłącznie do wg0.
Którego klienta naprawić najpierw, gdy kilka nie działa?
Należy naprawić jednego klienta Linux, ponieważ jest to jedyna platforma ukazująca działający mechanizm. resolvectl status oraz tcpdump informują, który resolver odpowiedział i który interfejs obsłużył pakiet. Aplikacje mobilne i desktopowe stosują te same wartości DNS oraz AllowedIPs bez widocznej warstwy technicznej, więc po poprawnej konfiguracji klienta Linux kopiuje się ustawienia, których poprawność została już zweryfikowana.