SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-10-21

Sửa lỗi DNS qua WireGuard: 3 trường hợp thường gặp

Tunnel WireGuard đã lên nhưng DNS không phân giải hoặc truy vấn rò rỉ qua router nội bộ? Tìm đúng 1 trong 3 lỗi và sửa theo routing, resolver hoặc systemd.

Vì sao DNS hỏng ngay khi tunnel WireGuard hoạt động

DNS qua WireGuard hỏng theo 3 cách, và mỗi trường hợp có cách sửa riêng. Không phân giải được tên nào, hoặc tên vẫn phân giải nhưng truy vấn rời máy của bạn bên ngoài tunnel, hoặc trình quản lý resolver của chính client ghi đè cấu hình vài giây sau khi interface khởi động. Tunnel hầu như không bao giờ là nguyên nhân. Vấn đề nằm ở dòng cấu hình cho client biết phải hỏi resolver nào và routing quyết định các packet đến resolver đó đi theo đường nào.

WireGuard chuyển các IP packet và không biết gì về DNS (domain name system, dịch vụ chuyển các tên như example.com thành địa chỉ IP). Dòng DNS = trong block [Interface] của client không phải là cài đặt WireGuard. Dòng này được wg-quick đọc; đây là shell wrapper dùng để đưa interface lên. Sau đó wg-quick chỉnh sửa cấu hình resolver của client khi tunnel đang hoạt động và khôi phục cấu hình đó khi tunnel dừng tại wg-quick down. Vì vậy, mọi vấn đề bên dưới đều là vấn đề routing hoặc vấn đề wg-quick, không bao giờ là vấn đề cryptography. Nếu tunnel vẫn chưa được tạo, hãy bắt đầu với VPN WireGuard tự host trên VPS của bạn rồi quay lại trang này.

Trước tiên, hãy xác nhận tunnel hoạt động bình thường rồi mới đụng đến DNS.

sudo wg show
ping -c 3 10.8.0.1
ping -c 3 1.1.1.1

wg show phải liệt kê peer cùng latest handshake gần đây, và cả hai lệnh ping đều phải nhận được phản hồi. Nếu ping 1.1.1.1 bị timeout, nhiều khả năng bạn gặp vấn đề forwarding hoặc NAT (network address translation), không phải vấn đề DNS; thay đổi resolver config bao nhiêu cũng không giúp được. Firewall chặn ICMP cũng gây ra timeout tương tự, nên hãy xác nhận bằng curl -sI https://1.1.1.1 trước khi thay đổi các forwarding rule. Nếu ping có phản hồi nhưng throughput giảm mạnh khi bắt đầu có traffic thực, đó là một lỗi riêng. WireGuard chậm hầu như luôn bắt nguồn từ MTU chứ không phải từ bất kỳ nội dung nào trên trang này. Mọi ví dụ ở đây đều dùng 10.8.0.0/24 làm tunnel subnet và 10.8.0.1 làm địa chỉ tunnel của server. Hãy thay bằng giá trị của bạn.

Lỗi thứ nhất: không phân giải được gì vì resolver không bao giờ trả lời

Triệu chứng rất rõ. ping 1.1.1.1 hoạt động, còn curl https://example.com trả về:

curl: (6) Could not resolve host: example.com

Gửi truy vấn trực tiếp đến tunnel resolver từ client. dig nằm trong package dnsutils trên Ubuntu và Debian.

dig +short @1.1.1.1 example.com
dig +short @10.8.0.1 example.com

Lệnh đầu tiên trả về một địa chỉ, chứng tỏ packet đi được qua tunnel đến Internet. Lệnh thứ hai không trả về gì và in ra ;; communication timed out; no servers could be reached. Đây là toàn bộ chẩn đoán: client đang trỏ đến 10.8.0.1, nhưng 10.8.0.1 không trả lời trên UDP port 53.

Có 2 nguyên nhân. Hoặc server không chạy resolver, hoặc firewall trên server drop query trước khi query đến nơi. Kiểm tra cả 2 trên server.

sudo ss -ulnp | grep ':53'
sudo nft list ruleset

Resolver đang chạy và bind đúng sẽ hiển thị một dòng chứa 10.8.0.1:53 hoặc 0.0.0.0:53. Trên Ubuntu, nguyên nhân thường gây nhầm là 127.0.0.53:53: đây là systemd-resolved stub listener, bind vào một địa chỉ loopback và cố ý không cho các máy khác truy cập. Trỏ VPN client đến một server mà resolver duy nhất là stub này sẽ gây đúng timeout như trên.

Cách sửa là dùng resolver lắng nghe trên địa chỉ tunnel, đồng thời thêm một firewall rule cho phép peer truy cập resolver.

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'

Sau đó chỉ mở port cho traffic từ tunnel. Với nftables, thêm 2 dòng này vào chain input trong /etc/nftables.conf rồi reload bằng sudo systemctl reload nftables.

udp dport 53 iifname "wg0" accept
tcp dport 53 iifname "wg0" accept

Với ufw, sudo ufw allow in on wg0 to any port 53 thực hiện chức năng tương tự. Không bao giờ mở port 53 ra public Internet. Open recursive resolver sẽ bị scanner phát hiện trong vài ngày và bị dùng để khuếch đại các cuộc tấn công từ chối dịch vụ. Provider sẽ phát hiện traffic đó trước bạn.

Chạy lại dig +short @10.8.0.1 example.com từ client. Nếu output có một địa chỉ, đường dẫn đến resolver đã hoạt động. Khi đó client chỉ còn phải sử dụng resolver này. Thêm dòng đó vào block [Interface] của client rồi restart interface bằng 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.1

Lỗi thứ hai: DNS bị rò rỉ vì split tunnel không định tuyến đến resolver

Lỗi này nghiêm trọng hơn vì mọi thứ trông có vẻ vẫn hoạt động. Tên miền vẫn được phân giải, trang vẫn tải, nhưng các truy vấn đi dưới dạng cleartext qua mạng cục bộ mà bạn không muốn tin cậy.

Có hai cấu hình gây ra lỗi này. Cấu hình đầu tiên là client có AllowedIPs = 0.0.0.0/0, ::/0 nhưng không có dòng DNS =. wg-quick cài default route vào routing table riêng của nó và thêm một rule bằng suppress_prefixlength 0. Cách này cố ý giữ các route cục bộ cụ thể hơn để máy vẫn có thể truy cập máy in. Resolver mà client nhận được qua DHCP, thường là router tại 192.168.1.1, khớp với một trong các route cục bộ đó. Lưu lượng của bạn đi qua tunnel. Mạng cục bộ vẫn nhận được đầy đủ danh sách tên miền bạn tra cứu.

Cấu hình thứ hai là split tunnel: AllowedIPs = 10.8.0.0/24 cùng với DNS = 9.9.9.9. Vì 9.9.9.9 không nằm trong AllowedIPs, client không có route đến resolver qua tunnel. Vì vậy, truy vấn đi qua link cục bộ, giống hệt trường hợp đầu tiên.

Hãy xác định resolver nào thực sự trả lời. whoami.akamai.net là một tên kiểm tra public. Tên này trả về địa chỉ IP của recursive resolver đã gửi truy vấn, nên bạn có thể so sánh kết quả với địa chỉ public của server.

resolvectl status
dig +short whoami.akamai.net
sudo tcpdump -ni any -c 10 port 53

resolvectl status in ra một block cho mỗi link. Nếu block của ethernet hoặc wireless link vẫn hiển thị Current DNS Server: 192.168.1.1 trong khi block wg0 không hiển thị resolver nào, đó là dấu hiệu rò rỉ. Nếu dig +short whoami.akamai.net trả về địa chỉ broadband tại nhà thay vì địa chỉ của server, kết quả này xác nhận lỗi từ phía đích. Dòng tcpdump là bằng chứng rõ nhất: output đúng phải đưa mọi packet đến port 53 rời máy qua wg0; khi bị rò rỉ, các packet đó đi qua wlan0 hoặc enp3s0. Các packet trên lo đến hoặc đi từ 127.0.0.53 là lưu lượng giữa ứng dụng và systemd-resolved stub trên máy cục bộ, nên không phải rò rỉ.

Cách sửa gồm hai phần và bắt buộc phải thực hiện cả hai. Đặt DNS thành một địa chỉ nằm trong tunnel, đồng thời bảo đảm địa chỉ đó nằm trong 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/16

10.8.0.1 nằm trong 10.8.0.0/24, nên truy vấn được mã hóa và gửi đến server. Nếu bạn vẫn muốn dùng public resolver trên split tunnel, hãy thêm resolver đó dưới dạng host route: AllowedIPs = 10.8.0.0/24, 9.9.9.9/32. Khi đó, các packet sẽ đi qua tunnel, nhưng mạng cục bộ vẫn có thể biết bạn đã chọn nhà cung cấp đó từ các phiên trước. Tự vận hành resolver sẽ tránh được vấn đề này.

Việc gán resolver là một khác biệt dễ thấy giữa WireGuard tự cấu hình và mesh được điều phối. Đây là một phần của đánh đổi được trình bày trong WireGuard so với Tailscale. Chạy control server Headscale tự host cho phép bạn có cơ chế điều phối đó mà không phải giao key material cho bên thứ ba. Nếu đây là điều khiến bạn lo ngại, hãy lưu ý rằng Tailscale không bao giờ giữ các key dùng để mã hóa lưu lượng của bạn, và câu hỏi đáng quan tâm hơn là một coordination server bị breach hoặc một tài khoản identity bị đánh cắp có thể thêm gì vào mạng của bạn.

Lỗi thứ ba: resolvconf và systemd-resolved xung đột trên các client Linux

Các client macOS, Windows, iOS và Android áp dụng DNS = thông qua app chính thức nên thường không gây nhiều vấn đề. Linux mới là nơi thiết lập này được áp dụng bằng shell script, và script phải đoán bạn đang dùng trình quản lý resolver nào trong số nhiều lựa chọn.

Lỗi đầu tiên dễ nhận thấy. sudo wg-quick up wg0 dừng với:

resolvconf: command not found

wg-quick gọi resolvconf, nhưng binary đó chưa được cài đặt. Trên Debian 12 và Ubuntu 24.04 trở lên, lệnh resolvconf giao tiếp với systemd-resolved nằm trong package systemd-resolved. Hãy cài package đó, rồi đưa interface lên lại. Ubuntu 22.04 không có package riêng cho lệnh này. Trên phiên bản đó, hãy tự tạo compatibility link bằng sudo ln -s /usr/bin/resolvectl /usr/local/bin/resolvconf và bỏ qua dòng cài đặt.

sudo apt install -y systemd-resolved
sudo systemctl restart systemd-resolved
sudo wg-quick down wg0 && sudo wg-quick up wg0
resolvectl status wg0

Lỗi thứ hai không biểu hiện rõ, nhưng thường làm mất cả buổi tối để xử lý. Interface đã lên, resolvectl status wg0 hiển thị đúng DNS Servers: 10.8.0.1, nhưng các truy vấn vẫn đi đến resolver cũ. systemd-resolved không bỏ qua wg0. Tên không khớp với routing domain nào sẽ được gửi đến DNS server của mọi link được tính là default route. Một link có DNS server nhưng không có route-only domain mặc định được tính là một default route. Vì vậy, truy vấn được gửi đồng thời đến 10.8.0.1 và router trên wireless link, rồi câu trả lời thành công đầu tiên sẽ được dùng. Router chỉ cách một hop và thường trả lời trước. Dù thế nào, truy vấn cũng đã rời máy dưới dạng cleartext. Các tên nằm trong search domain riêng của wireless link, chẳng hạn lan, chỉ được gửi đến link đó vì routing domain cụ thể nhất sẽ được ưu tiên.

Khi wg-quick áp dụng DNS = thông qua wrapper resolvconf riêng của systemd, nó đã yêu cầu routing domain bắt tất cả. Vì vậy, lỗi này chủ yếu xảy ra khi một implementation resolvconf khác hoặc một network manager áp dụng thiết lập. Đặt rõ giá trị này sẽ loại bỏ việc phải đoán.

Hãy đặt resolver và khai báo routing domain bắt tất cả trong cùng một bước. %i được thay bằng tên interface, nên block này hoạt động nguyên trạng trên mọi interface.

[Interface]
Address = 10.8.0.2/32
PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~.
PostDown = resolvectl revert %i

Hãy xóa dòng DNS = khi dùng PostUp theo cách này. Nếu không, hai cơ chế sẽ cùng ghi trạng thái resolver và chỉ một cơ chế dọn dẹp sau đó. Đối số ~. là phần quan trọng: nó biến wg0 thành route-only domain cho mọi tên. Nhờ đó, systemd-resolved không còn gửi các tên không khớp với domain cụ thể hơn đến các link khác. Các tên thuộc domain riêng của link khác, chẳng hạn lan, vẫn được gửi đến link đó. Đây thường là hành vi cần có đối với host nội bộ. Hãy kiểm tra.

resolvectl status wg0

Output đúng phải chứa DNS Servers: 10.8.0.1 và DNS Domain: ~.. Nếu thiếu ~., phần resolvectl domain chưa chạy và wg0 lại tiếp tục cạnh tranh với các link khác. Không dùng flag default route để kiểm tra. Flag này vẫn hiển thị yes hoặc +DefaultRoute trên dòng Protocols, tùy phiên bản systemd, ngay cả khi không có ~.. Nguyên nhân là link không có route-only domain vốn đã được xem là default route.

Còn một trường hợp cần lưu ý. Nếu /etc/resolv.conf là file thật thay vì symlink đến /run/systemd/resolve/stub-resolv.conf, một thành phần khác đang quản lý file đó, thường là NetworkManager hoặc container runtime. Hãy chạy ls -l /etc/resolv.conf trước khi debug việc khác, vì tool ghi lại file này sau mỗi thay đổi mạng sẽ hoàn tác cấu hình của bạn vào thời điểm bất tiện nhất.

Nâng cấp: resolver tự lọc của bạn qua tunnel

Khi query đã đi ổn định qua tunnel, resolver ở đầu bên kia trở thành một điểm kiểm soát. Chạy AdGuard Home tại đó sẽ cung cấp tính năng lọc bằng blocklist và query log cho mọi thiết bị đã kết nối, không cần cài phần mềm trên client và không cần cấu hình riêng cho từng thiết bị. Script cài đặt chính thức, được kiểm tra vào October 2026, chỉ có một dòng.

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

Wizard thiết lập lắng nghe trên port 3000 trong lần chạy đầu tiên. Truy cập nó qua tunnel tại http://10.8.0.1:3000 thay vì public port đó, rồi trong wizard đặt cả địa chỉ lắng nghe DNS và địa chỉ lắng nghe admin thành 10.8.0.1. Nếu unbound từ lỗi thứ nhất vẫn đang giữ cùng địa chỉ, hãy dừng nó trước bằng sudo systemctl disable --now unbound, vì hai tiến trình sẽ tranh chấp UDP port 53 trên cùng một địa chỉ và AdGuard Home sẽ thoát với listen udp 10.8.0.1:53: bind: address already in use.

Không cần thay đổi cấu hình client nếu chúng đã đặt là DNS = 10.8.0.1. Query log lúc này sẽ hiển thị mọi lookup đi tới resolver từ mọi peer, trừ khi bạn loại một client khỏi việc ghi log. Đây là một quyết định thực sự về quyền riêng tư, không phải lợi ích miễn phí: bạn chuyển nơi tin cậy từ nhà cung cấp Internet sang chính mình, và bạn phải tự giữ cho máy đó được cập nhật bản vá. Một server public Internet cần được thiết lập các biện pháp cơ bản trước, và mười phút đầu tiên trên một VPS mới trình bày các bước đó.

FAQ

Vì sao tunnel WireGuard kết nối được nhưng không phân giải được tên?

Tunnel chỉ truyền packet và hoàn toàn không xử lý tên. Vì vậy, nếu tunnel hoạt động nhưng lookup lỗi thì resolver mà bạn trỏ đến không trả lời. Kiểm tra bằng dig +short @10.8.0.1 example.com trên client. Phản hồi communication timed out nghĩa là không có resolver nào đang listen trên địa chỉ của tunnel. Nguyên nhân thường là stub của systemd-resolved chỉ bind vào 127.0.0.53, hoặc firewall trên server đang drop UDP port 53 đi vào wg0. Trước tiên, hãy sửa listener. Sau đó chỉ mở port cho wg0.

Làm cách nào để kiểm tra DNS có bị leak qua WireGuard hay không?

Chạy sudo tcpdump -ni any -c 10 port 53 trên client và theo dõi cột interface trong khi duyệt web. Mọi packet rời khỏi máy phải đi qua wg0. Các packet trên lo đến hoặc đi từ 127.0.0.53 là stub systemd-resolved cục bộ và không có vấn đề gì. Nếu packet xuất hiện trên interface wireless hoặc ethernet, query đang rời máy dưới dạng plaintext. dig +short whoami.akamai.net cung cấp thêm một cách kiểm tra, vì nó trả về địa chỉ public của recursive resolver đã thực hiện query. Vì vậy, nếu địa chỉ trả về không phải địa chỉ của server thì DNS đang bị leak.

Tôi có cần dòng DNS = nếu dùng split tunnel không?

Có. Địa chỉ resolver cũng phải nằm trong AllowedIPs, nếu không client không có route đến resolver đó. Với AllowedIPs = 10.8.0.0/24, resolver tại 10.8.0.1 được bao phủ và query được mã hóa. Public resolver như 9.9.9.9 không được bao phủ. Vì vậy, query sẽ đi qua local link dù dòng DNS có vẻ đúng.

Vì sao resolvectl hiển thị đúng server nhưng lookup vẫn đi nơi khác?

systemd-resolved gửi tên không khớp với routing domain đến tất cả link có default route cùng lúc, bao gồm wg0, rồi trả về câu trả lời thành công đầu tiên. Câu trả lời này thường đến từ local router. Thêm PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~. vào block [Interface] của client và xóa dòng DNS =. Sau đó, resolvectl status wg0 phải báo cáo DNS Domain: ~.. Mọi tên không được link nào khác nhận bằng domain cụ thể hơn sẽ chỉ được gửi đến wg0.

Tôi nên sửa client nào trước khi có nhiều client bị lỗi?

Hãy sửa một client Linux trước, vì đây là platform duy nhất cho thấy cơ chế hoạt động. resolvectl status và tcpdump cho biết resolver nào đã trả lời và interface nào đã truyền packet. Ứng dụng trên điện thoại và desktop áp dụng cùng các giá trị DNS và AllowedIPs nhưng không hiển thị phần xử lý bên dưới. Vì vậy, khi client Linux hoạt động đúng, bạn chỉ cần sao chép một cấu hình đã được kiểm chứng.

Bài viết này có hữu ích không?