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

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 khắc phục riêng. Có thể 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 đi ra ngoài tunnel, hoặc resolver manager 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 cần hỏi resolver nào và routing quyết định các packet đến resolver đó đi theo đường nào.

WireGuard chỉ chuyển tiếp 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 client [Interface] không phải là một thiết lập WireGuard. Dòng này được wg-quick đọc, tức shell wrapper dùng để đưa interface lên, còn wg-quick sẽ chỉnh sửa cấu hình resolver của client trong lúc tunnel đang hoạt động và khôi phục cấu hình đó khi tunnel được đưa xuống bằng 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 phải vấn đề cryptography. Nếu tunnel 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 kiểm tra 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 với 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, bạn đang gặp vấn đề forwarding hoặc NAT (network address translation), không phải vấn đề DNS; không có cấu hình resolver nào có thể khắc phục được. Nếu ping có phản hồi nhưng throughput sụt mạnh khi bắt đầu truyền traffic thực tế, đó là một lỗi riêng, và WireGuard chậm hầu như luôn liên quan đến MTU chứ không liên quan đến nội dung trong 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: rò rỉ DNS vì split tunnel không định tuyến đến resolver

Lỗi này nghiêm trọng hơn vì mọi thứ vẫn có vẻ hoạt động. Tên miền vẫn được phân giải, trang web 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ó 2 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 rule với 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 printer. 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ộ đó. Traffic 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 với DNS = 9.9.9.9. Vì 9.9.9.9 không nằm trong AllowedIPs, client không có route đến địa chỉ đó qua tunnel. Vì vậy, truy vấn đi qua kết nối cục bộ, giống hệt trường hợp đầu tiên.

Hãy xác minh resolver nào thực sự trả lời. whoami.akamai.net là một tên kiểm tra public, trả về địa chỉ IP của recursive resolver đã gửi truy vấn. Bạn có thể so sánh địa chỉ đó 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 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à rò rỉ. dig +short whoami.akamai.net trả về địa chỉ broadband tại nhà thay vì địa chỉ của server cũng xác nhận lỗi từ phía đích. Dòng tcpdump là bằng chứng cuối cùng: output đúng phải đưa mọi packet đến port 53 qua wg0, còn khi bị rò rỉ, chúng đi qua wlan0 hoặc enp3s0.

Cách sửa gồm 2 phần và phải thực hiện cả 2. Đặt DNS thành một địa chỉ nằm bên 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 provider đó từ các session trước. Tự vận hành resolver sẽ tránh được vấn đề này.

Việc gán resolver là một trong những khác biệt dễ thấy giữa WireGuard tự cấu hình thủ công và một mesh được điều phối. Đây là một phần của sự đánh đổi trong WireGuard so với Tailscale. Chạy Headscale control server tự host giú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 chính ý cuối cùng này 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 traffic của bạn. Câu hỏi thực tế hơn là một coordination server bị breach hoặc một identity account bị đánh cắp có thể bổ sung gì vào network của bạn.

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

Các client macOS, Windows, iOS và Android áp dụng DNS = thông qua app chính thức và gần như không gây vấn đề. Linux mới là nơi thiết lập này được áp dụng bằng một shell script phải đoán bạn đang chạy resolver manager nào trong số vài loại khác nhau.

Lỗi đầu tiên rất rõ ràng. 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. Hãy cài implementation giao tiếp với systemd-resolved, sau đó bật lại interface.

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

Lỗi thứ hai không hiển thị rõ. Đây là lỗi khiến bạn mất cả buổi tối để xử lý. Interface hoạt động, 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 duy trì một danh sách resolver riêng cho từng link và chọn link cho mỗi truy vấn. Nếu không có link nào được đánh dấu là default route cho name, nó vẫn dùng resolver của wireless link, vì link đó có search domain còn link của bạn thì không.

Đặt resolver và xác nhận default route 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

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 resolver state và chỉ một cơ chế thực hiện cleanup sau đó. Đối số ~. là phần quan trọng: nó đánh dấu wg0 là routing domain cho mọi name, nên systemd-resolved gửi toàn bộ truy vấn đến đó thay vì chọn link cho từng truy vấn. Hãy xác minh.

resolvectl status wg0

Output đúng phải chứa DNS Servers: 10.8.0.1Default Route: yes. Nếu Default Route hiển thị no, phần resolvectl domain chưa chạy và hệ thống quay lại cơ chế chọn link.

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 vấn đề khác, vì một tool ghi đè 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 không thích hợp.

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

Khi các query đã truyền ổn định qua tunnel, resolver ở đầu xa 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 phía 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 July 2026, chỉ gồm một dòng.

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

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

Không cần thay đổi client config nếu chúng đã có DNS = 10.8.0.1. Query log giờ sẽ hiển thị mọi lookup từ mọi peer. Đâ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 niềm tin từ nhà cung cấp Internet sang chính mình, và bạn phải tự đảm bảo máy đó luôn được cập nhật bản vá. Một server public trên Internet trước tiên phải được cấu hình các biện pháp cơ bản, và 10 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, nên tunnel hoạt động nhưng lookup lỗi có nghĩa là resolver 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 có nghĩa là không có resolver nào đang listen trên địa chỉ của tunnel, thường do stub của systemd-resolved chỉ bind vào 127.0.0.53, hoặc firewall của server đang drop UDP port 53 đi vào từ 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 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 phải đi qua wg0. Nếu chúng xuất hiện trên interface wireless hoặc ethernet, các 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ề public address 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, DNS chắc chắn đang bị leak.

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

Có, và địa chỉ resolver cũng phải nằm trong AllowedIPs, nếu không client không có route đến địa chỉ đó. 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ủ, nên query đi ra 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 duy trì một danh sách resolver cho từng link và chọn một link cho mỗi query, nên entry đúng trên wg0 sẽ bị bỏ qua khi một link khác đang giữ default route cho việc phân giải tên. 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 =. Khi đó resolvectl status wg0 sẽ báo Default Route: yes.

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à nền tảng duy nhất cho thấy cơ chế hoạt động. resolvectl statustcpdump 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ị DNSAllowedIPs nhưng không hiển thị phần plumbing, nên khi client Linux đã đúng, bạn chỉ cần sao chép một cấu hình đã được kiểm chứng.