SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor

WireGuardのDNSが使えない3つの原因と対処法

WireGuardは接続済みなのに名前解決できない、またはDNSクエリがローカルルーターへ漏れる問題を、3つの失敗パターン別に確認して直します。

WireGuard トンネルの確立直後に DNS が機能しなくなる理由

WireGuard 経由の DNS は、3 通りの原因で失敗します。それぞれに対処方法があります。何も名前解決できない場合、名前解決はできてもクエリがトンネルの外へ出ていく場合、またはインターフェースの起動から数秒後にクライアント自身の resolver manager が設定を上書きする場合です。トンネル自体が原因であることはほとんどありません。問題は、クライアントに問い合わせ先の resolver を指定する 1 行と、その resolver までパケットをどの経路で送るかを決めるルーティングです。

WireGuard は IP パケットを転送しますが、DNS(example.com などの名前を IP アドレスに変換する domain name system サービス)については何も認識しません。クライアントの [Interface] ブロックにある DNS = 行は、WireGuard の設定ではありません。この行は、インターフェースを起動する shell wrapper である wg-quick が読み取り、wg-quick がトンネルの稼働中にクライアントの resolver 設定を変更し、wg-quick down で元に戻します。したがって、以下の問題はすべてルーティングの問題または wg-quick の問題であり、暗号化の問題ではありません。トンネル自体をまだ構築していない場合は、まず 自分の VPS で self-hosted WireGuard VPN を構築する方法から始め、その後このページに戻ってください。

DNS を変更する前に、トンネルが正常であることを確認します。

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

wg show に最近の latest handshake とともに peer が表示され、2 つの ping がどちらも応答することを確認します。ping 1.1.1.1 がタイムアウトする場合は、DNS の問題ではなく forwarding または NAT(network address translation)の問題である可能性が高く、resolver の設定をいくら変更しても解決しません。ICMP を破棄する firewall でも同じタイムアウトが発生するため、forwarding ルールを変更する前に curl -sI https://1.1.1.1 で確認してください。ping が応答しても、実際のトラフィックを開始するとスループットが急低下する場合は、別の問題です。この場合は、このページの内容ではなく、WireGuard が遅い場合の原因はほぼ常に MTU にあるを確認してください。ここでの例では、トンネルのサブネットに 10.8.0.0/24、サーバーのトンネルアドレスに 10.8.0.1 を使用します。自分の環境に合わせて置き換えてください。

障害その1: リゾルバーが応答しないため、名前解決がまったく機能しない

症状は明確です。ping 1.1.1.1 は動作しますが、curl https://example.com は次の内容を返します。

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

クライアントからトンネルのリゾルバーに直接問い合わせます。dig は Ubuntu と Debian の dnsutils パッケージに含まれています。

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

最初のコマンドがアドレスを返すことから、パケットがトンネル経由でインターネットに到達していることを確認できます。2 番目のコマンドは何も返さず、;; communication timed out; no servers could be reached を出力します。診断結果はこれで確定です。クライアントは 10.8.0.1 を参照するよう設定されていますが、10.8.0.1 は UDP port 53 で応答していません。

原因は 2 つあります。サーバーでリゾルバーが動作していないか、サーバーのファイアウォールがクエリを到達前に破棄しています。サーバーで両方を確認します。

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

正常に動作し、正しく bind されたリゾルバーでは、10.8.0.1:53 または 0.0.0.0:53 を含む行が表示されます。Ubuntu でよくある問題は 127.0.0.53:53 です。これは systemd-resolved の stub listener で、loopback address に bind し、他のマシンからは意図的に到達できません。唯一のリゾルバーがこの stub であるサーバーを VPN client の参照先にすると、まったく同じ timeout が発生します。

トンネル address で待ち受けるリゾルバーを用意し、peers から到達できるように firewall rule を 1 つ追加します。

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'

次に、トンネルの traffic に限定して port を開きます。nftables では、/etc/nftables.conf の input chain に次の 2 行を追加し、sudo systemctl reload nftables で reload します。

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

ufw では、sudo ufw allow in on wg0 to any port 53 で同じ設定になります。port 53 を public internet に開放してはいけません。公開された recursive resolver は数日以内に scanner に発見され、denial of service attack の増幅に使われます。その traffic をあなたより先に provider が検知する可能性もあります。

クライアントから dig +short @10.8.0.1 example.com を再実行します。出力に address が表示されれば、resolver path は機能しています。あとは client の [Interface] block にこの行を追加し、sudo wg-quick down wg0 && sudo wg-quick up wg0 で interface を再起動します。

[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1

DNS リーク: split tunnel が resolver への経路を設定しない場合

この問題は、すべてが正常に動作しているように見えるため、さらに深刻です。名前解決は成功し、ページも読み込めます。しかし、クエリは信頼したくなかったローカルネットワーク上を平文で通過します。

原因になる構成は2つあります。1つ目は、AllowedIPs = 0.0.0.0/0, ::/0 が設定されている一方で DNS = 行がないクライアントです。wg-quick は自身の routing table に default route を追加し、suppress_prefixlength 0 を指定した rule も追加します。これは、マシンがプリンターに接続できるように、より具体的なローカル経路を意図的に維持するためです。クライアントが DHCP で取得した resolver は、通常 192.168.1.1 の router ですが、これらのローカル経路のいずれかに一致します。通信自体は tunnel を通ります。しかし、ローカルネットワークには、検索した名前の一覧がすべて届きます。

2つ目は、AllowedIPs = 10.8.0.0/24 と DNS = 9.9.9.9 を使用する split tunnel です。9.9.9.9 は AllowedIPs の範囲内にないため、クライアントには tunnel 経由でそこへ到達する経路がありません。そのため、クエリは1つ目の場合と同じようにローカルリンクから送信されます。

実際にどの resolver が応答しているかを確認します。whoami.akamai.net は、問い合わせた recursive resolver の IP アドレスを返す公開テスト名です。その応答を、server の公開アドレスと比較できます。

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

resolvectl status は、link ごとに1つのブロックを表示します。ethernet または wireless link のブロックに Current DNS Server: 192.168.1.1 が表示され、wg0 のブロックには何も表示されない場合、それがリークです。dig +short whoami.akamai.net が server のアドレスではなく自宅の broadband アドレスを返せば、外部からもリークを確認できます。問題を決着させる証拠になるのが tcpdump 行です。正常な出力では、マシンから送信される port 53 のパケットはすべて wg0 を通ります。リークがある場合は、wlan0 または enp3s0 を通ります。lo 上で 127.0.0.53 との間を移動するパケットは、アプリケーションがローカルの systemd-resolved stub と通信しているものです。これはリークではありません。

修正には2つの手順があり、両方が必要です。DNS を tunnel 内のアドレスに設定し、そのアドレスが 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 は 10.8.0.0/24 の範囲内にあるため、クエリは暗号化されて server に送信されます。split tunnel で公開 resolver を使用する場合は、host route として追加します: AllowedIPs = 10.8.0.0/24, 9.9.9.9/32。これでパケットは tunnel を通ります。ただし、以前のセッションから、ローカルネットワークにはその provider を選択したことが分かる可能性があります。自分で運用する resolver を使用すれば、この問題を避けられます。

resolver の割り当ては、手動で構築した WireGuard と協調型 mesh の違いが表れる部分の1つです。これは WireGuard と Tailscale の比較 におけるトレードオフの一部です。self-hosted Headscale control server を運用すれば、key material を第三者に渡さずに、この協調機能を利用できます。最後の点が懸念になる場合は、Tailscale は通信を暗号化する key を保持しないことに注意してください。より重要な問題は、侵害された coordination server や盗まれた identity account によって、ネットワークへ何が追加され得るかです。

Linux クライアントで resolvconf と systemd-resolved が競合する

macOS、Windows、iOS、Android のクライアントは公式アプリを通じて DNS = を適用するため、問題はほとんど発生しません。Linux では、複数ある resolver manager のどれを使っているかを shell script が推測して設定を適用します。

最初の失敗は明確です。sudo wg-quick up wg0 は次のエラーで停止します。

resolvconf: command not found

wg-quick は resolvconf を呼び出しますが、その binary がインストールされていません。Debian 12 と Ubuntu 24.04 以降では、systemd-resolved と通信する resolvconf command は systemd-resolved package に含まれています。そのため、これをインストールしてから interface を再度起動します。Ubuntu 22.04 には専用 package がありません。その場合は、sudo ln -s /usr/bin/resolvectl /usr/local/bin/resolvconf で互換 link を自分で作成し、install line は省略します。

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

2 つ目の失敗は気付きにくく、調査に時間がかかります。interface は起動し、resolvectl status wg0 には正しく DNS Servers: 10.8.0.1 と表示されるのに、名前解決は古い resolver に到達します。systemd-resolved が wg0 を無視しているわけではありません。routing domain に一致しない名前は、default route として扱われるすべての link の DNS server に送られます。DNS server があり、route-only domain がない link は、デフォルトで default route として扱われます。そのため、query は 10.8.0.1 と wireless link 上の router に同時に送られ、最初に成功した応答が採用されます。router は 1 hop 先にあり、通常はこちらが先に応答するため、どちらの場合も query はすでに平文で送信されています。wireless link 固有の search domain 配下にある lan などの名前は、最も具体的な routing domain が優先されるため、その link にだけ送られます。

wg-quick が systemd 独自の resolvconf wrapper を通じて DNS = を適用する場合、すでに catch-all routing domain が要求されます。そのため、この失敗は主に別の resolvconf implementation または network manager が設定を適用した場合に発生します。明示的に設定すれば推測は不要です。

同じ手順で resolver を設定し、catch-all routing domain を割り当てます。%i は interface name に展開されるため、この block はどの interface でも変更せずに使えます。

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

この方法で PostUp を使う場合は DNS = line を削除します。削除しないと、2 つの mechanism が resolver state を書き込み、後で cleanup するのは一方だけになるためです。重要なのは ~. argument です。これにより、wg0 がすべての名前に対する route-only domain になります。その結果、より具体的な domain に一致しない名前が他の link に送られなくなります。lan など、別の link 固有の domain 配下にある名前は、引き続きその link に送られます。通常、local host にはこの動作が適しています。確認します。

resolvectl status wg0

正常な出力には DNS Servers: 10.8.0.1 と DNS Domain: ~. が含まれます。~. がない場合、resolvectl domain の処理が実行されておらず、wg0 は再び他の link と競合しています。確認に default route flag を使わないでください。~. がなくても、yes(または systemd version によっては Protocols line 上の +DefaultRoute)と表示されるためです。route-only domain のない link は、いずれにせよ default route として扱われます。

もう 1 つ、注意すべきケースがあります。/etc/resolv.conf が /run/systemd/resolve/stub-resolv.conf への symlink ではなく実体のある file である場合、通常は NetworkManager または container runtime など、別の component がその file を管理しています。他の調査を始める前に ls -l /etc/resolv.conf を実行してください。network change のたびにその file を書き換える tool があると、最も避けたいタイミングで設定が元に戻されるためです。

アップグレード: トンネル上で独自のフィルタリングリゾルバーを運用する

クエリがトンネルを確実に通過するようになると、接続先のリゾルバーが制御ポイントになります。そこに AdGuard Home を導入すると、接続されたすべてのデバイスで、クライアントソフトウェアやデバイスごとの設定なしに、ブロックリストによるフィルタリングとクエリログを利用できます。2026年10月に確認した公式インストールスクリプトは、1行で実行できます。

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

初回起動時、セットアップウィザードはポート 3000 で待ち受けます。このポートを外部公開せず、トンネル経由で http://10.8.0.1:3000 にアクセスしてください。ウィザードでは、DNS の待ち受けアドレスと管理画面の待ち受けアドレスの両方を 10.8.0.1 に設定します。障害1で発生した unbound がまだ同じアドレスを使用している場合は、先に sudo systemctl disable --now unbound で停止してください。同じアドレス上の UDP ポート 53 を2つのプロセスが競合して使用するため、AdGuard Home は listen udp 10.8.0.1:53: bind: address already in use で終了します。

クライアント設定がすでに DNS = 10.8.0.1 になっていれば、変更は不要です。これでクエリログには、各ピアから到達したすべての名前解決要求が表示されます。ただし、クライアントをログの対象外にすることもできます。これは無条件に得られる利点ではなく、プライバシーに関わる判断です。信頼先をインターネットプロバイダーから自分自身に移すため、そのサーバーにパッチを適用し続ける責任も自分にあります。インターネットに公開するサーバーでは、まず基本的な対策を整える必要があります。新しい VPS で最初の10分間に行う作業で、その手順を説明しています。

FAQ

WireGuard トンネルは接続できるのに、名前を解決できないのはなぜですか?

トンネルはパケットを転送しますが、名前の処理は行いません。そのため、トンネルが動作しているのに名前解決に失敗する場合は、指定したリゾルバーが応答していません。クライアントで dig +short @10.8.0.1 example.com を実行して確認します。communication timed out の応答は、トンネルのアドレスでリゾルバーが待ち受けていないことを示します。systemd-resolved のスタブが 127.0.0.53 にしかバインドしていないことが原因の場合が多くあります。または、サーバーのファイアウォールが wg0 に到着した UDP port 53 を破棄している可能性があります。まず待ち受けを修正し、その後 wg0 に対してのみ port を開放します。

DNS が WireGuard 経由で漏えいしているか確認するにはどうすればよいですか?

クライアントで sudo tcpdump -ni any -c 10 port 53 を実行し、ブラウジング中にインターフェース列を確認します。マシンから出るすべてのパケットは wg0 上にある必要があります。lo 上で 127.0.0.53 との間を行き来するパケットは、ローカルの systemd-resolved スタブによるものなので問題ありません。無線または ethernet のインターフェースに表示される場合、クエリは平文で送信されています。dig +short whoami.akamai.net でも確認できます。これは問い合わせを行った recursive resolver の公開アドレスを返すため、サーバーのアドレス以外が返れば DNS が漏えいしています。

split tunnel を使用する場合、DNS = 行は必要ですか?

必要です。また、リゾルバーのアドレスも AllowedIPs の範囲内に含める必要があります。そうしないと、クライアントにそのアドレスへの route がありません。AllowedIPs = 10.8.0.0/24 を使用すると、10.8.0.1 のリゾルバーが対象になり、クエリは暗号化されます。9.9.9.9 のような public resolver は対象外です。そのため、DNS 行が正しく見えても、クエリはローカルリンク経由で送信されます。

resolvectl では正しい server が表示されるのに、名前解決が別の場所へ送られるのはなぜですか?

systemd-resolved は、どの routing domain にも一致しない名前を、wg0 を含むすべての default-route link に同時に送信します。そして、最初に成功した応答を返します。通常はローカル router からの応答になります。クライアントの [Interface] block に PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~. を追加し、DNS = 行を削除します。その後、resolvectl status wg0 は DNS Domain: ~. を報告します。ほかの link がより具体的な domain を指定していない名前は、すべて wg0 にのみ送信されます。

複数のクライアントが壊れている場合、最初にどのクライアントを修正すべきですか?

Linux クライアントを1台修正します。仕組みを確認できるのは Linux クライアントだけだからです。resolvectl status と tcpdump で、どの resolver が応答し、どの interface がパケットを転送したかを確認できます。Phone と desktop のアプリは、同じ DNS と AllowedIPs の値を使用しますが、内部の処理は表示しません。そのため、Linux クライアントが正しく動作すれば、すでに検証済みの構成をコピーできます。