
Unboundとは、オランダのNLnet Labsが開発しているオープンソースのキャッシュDNSサーバーで、LAN内の端末から名前解決の問い合わせを受けて答えを探し、その結果をキャッシュして返すソフトウェアです。DNSSECの署名検証とDNS over TLS(DoT)での問い合わせ転送を標準で備え、設定ファイル1つで自宅や小さなオフィスのDNSサーバーを構築できます。
ここではUbuntu 24.04のパッケージ(Unbound 1.19.2、執筆時点)を使い、上位の問い合わせをDoTで暗号化し、DNSSECを検証し、nas.home.arpa のようなLAN内の名前も返すキャッシュDNSサーバーを作ります。設定はすべてコンテナの中で実際に起動し、digの結果で確かめています。
この記事でわかること
- 権威DNSとキャッシュDNSの違いと、BINDではなくUnboundを選ぶ理由
- ルートから自分で辿る「フルリゾルバー」とDoTでの転送のどちらにするかの判断基準
- Ubuntu 24.04のパッケージ構成と、そのまま使える設定ファイル一式
- DNSSEC検証とLAN内の名前(home.arpa)を確かめるdigの読み方
- REFUSED、SERVFAIL、bindエラーが出たときの原因と直し方
Unboundとは:権威DNSサーバーとキャッシュDNSサーバーの違い
DNSサーバーには役割の違う2種類があります。自分が管理するドメイン(ゾーン)について答えるのが権威DNSサーバー、端末の代わりにあちこちの権威サーバーへ問い合わせて答えを集め、キャッシュするのがキャッシュDNSサーバー(フルリゾルバー)です。このソフトは後者専用で、自分のゾーンを外部に公開する機能は持ちません。
| 種類 | 役割 | 代表的なソフト | 自宅・小規模オフィスでの置き方 |
|---|---|---|---|
| 権威DNSサーバー | 自分のゾーンについて答える | NSD、Knot DNS、BIND、PowerDNS | 公開ドメインはレジストラやDNS事業者に任せる |
| キャッシュDNSサーバー | 端末の代わりに答えを探し、キャッシュする | Unbound、BIND、PowerDNS Recursor | LANに1台置き、DHCPで配る |
| フォワーダー | 受けた問い合わせを別のリゾルバーに渡す | dnsmasq、systemd-resolved、家庭用ルーター | 端末やルーターに最初から入っている |
BINDは権威とキャッシュの両方を1つのプロセスで扱えますが、そのぶん設定項目が多く、両方の役割を同じサーバーに載せると、片方の設定ミスや脆弱性がもう片方に波及します。LANのキャッシュDNSとして使うだけなら、機能を絞ったUnboundの方が設定が短く、Ubuntuのパッケージでは最初からDNSSEC検証が有効になっています。逆に、動的更新を受け付ける社内ゾーンを本格的に運用したい場合(Active Directoryの名前解決など)は、Unboundの local-data では足りないので、別の製品を選びます。
2つの動かし方:フルリゾルバーとDNS over TLSでの転送
Unboundは、ルートサーバーから順に権威サーバーを辿る「フルリゾルバー」としても、決めた公開リゾルバーへ問い合わせを丸ごと渡す「フォワーダー」としても動きます。フォワーダーにする場合は、転送をDNS over TLS(TCP 853番のTLS)で暗号化できます。
| 観点 | フルリゾルバー | DoTで転送 |
|---|---|---|
| 問い合わせ先 | ルートから順に各ドメインの権威サーバー | 指定した公開リゾルバー(例:1.1.1.1) |
| 経路の暗号化 | なし(53番の平文) | あり(853番のTLS) |
| 問い合わせの履歴が見える相手 | 経路上のISPと、各権威サーバー(それぞれ一部) | 転送先の事業者(すべて) |
| DNSSECの検証 | Unboundが行う | Unboundが行う(転送先の判定を鵜呑みにしない) |
| 必要な通信 | UDP/TCP 53番の外向き | TCP 853番の外向き |
応答速度はどちらを選んでも大差ありません。この検証環境で同じ3つの名前を引いたところ、キャッシュのない初回はどちらの方式も14〜160ミリ秒の範囲でばらつき、2回目以降はすべて0ミリ秒でした。決め手は速さではなく、問い合わせ履歴を誰に見せるかです。ISPに平文の問い合わせを見せたくないならDoT転送、特定の事業者に履歴を集約させたくないならフルリゾルバーを選びます。迷ったらDoT転送から始め、あとで forward-zone を消せばフルリゾルバーに切り替えられます。
インストールとファイル構成(Ubuntu 24.04)
sudo apt update
sudo apt install unbound dnsutils
unbound -V | head -1
# Version 1.19.2 ← 執筆時点のUbuntu 24.04の版Ubuntuのパッケージでは、/etc/unbound/unbound.conf には include-toplevel: "/etc/unbound/unbound.conf.d/*.conf" の1行しかなく、実際の設定は unbound.conf.d/ に分かれています。最初から入っているのは2つで、remote-control.conf は管理コマンド unbound-control をUNIXソケット(/run/unbound.ctl)経由で有効にし、root-auto-trust-anchor-file.conf はDNSSECの起点となるルートの鍵を /var/lib/unbound/root.key から読み、自動で更新するよう指定しています。systemdのユニットは起動前に unbound-helper root_trust_anchor_update を実行し、このファイルを用意します。
もう1つ気をつけるのが、127.0.0.53 と 127.0.0.54 の53番で待ち受けているsystemd-resolvedとの関係です。127.0.0.1 とLAN側のIPアドレスのように具体的なアドレスで待ち受けさせれば共存できますが、0.0.0.0 を指定するとポートが衝突して起動しません。この記事の設定は具体的なアドレスで書きます。
キャッシュDNSサーバーを構築する:設定ファイルを書く
自分の設定は unbound.conf.d/ に新しいファイルとして置きます。サーバーのLAN側アドレスを 192.168.10.2、LANを 192.168.10.0/24 とした例です。
server:
interface: 127.0.0.1
interface: 192.168.10.2
ip-freebind: yes
port: 53
access-control: 127.0.0.0/8 allow
access-control: 192.168.10.0/24 allow
hide-identity: yes
hide-version: yes
prefetch: yes
serve-expired: yes
serve-expired-ttl: 86400
msg-cache-size: 32m
rrset-cache-size: 64m
log-servfail: yes
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: fd00::/8
private-address: fe80::/10
private-domain: "home.arpa"
local-zone: "home.arpa." static
local-data: "nas.home.arpa. IN A 192.168.10.20"
local-data: "pve.home.arpa. IN A 192.168.10.11"
local-data-ptr: "192.168.10.20 nas.home.arpa"
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com上から順に意味を説明します。interface は待ち受けるアドレスで、ip-freebind: yes は、起動時にまだそのアドレスがインターフェースに付いていなくても待ち受けを始める設定です。ネットワークの準備より先にUnboundが起動して失敗する、という起動順の問題を避けられます。access-control に書いていない送信元からの問い合わせは拒否(REFUSED)されるので、LANの範囲を忘れずに書きます。
prefetch は、よく引かれる名前のTTLが切れる少し前に先回りして取り直す設定、serve-expired と serve-expired-ttl: 86400 は、上位に届かないときにTTL切れのキャッシュを最大1日分返し続ける設定です。回線が一時的に落ちても、普段使っているサイトの名前解決は止まりません。キャッシュサイズは、rrset-cache-size を msg-cache-size の2倍程度にするのが釣り合いの取れた割合です。log-servfail: yes は、SERVFAILを返した理由を1行でログに残します。
private-address は、外部のドメイン名の答えにプライベートIPが含まれていたら捨てる設定で、DNSリバインディング攻撃(外部のWebページからLAN内の機器を操作させる手口)を防ぎます。自分のドメインでLAN内のアドレスを返すように運用している場合は、そのドメインを private-domain に足して例外にします。CaddyでDNS-01を使ってLAN専用サーバーをHTTPS化する構成では、home.example.com をここに加えます。
最後の forward-zone がDoT転送です。1.1.1.1@853#cloudflare-dns.com は「1.1.1.1の853番にTLSで接続し、証明書が cloudflare-dns.com のものか検証する」という意味で、tls-cert-bundle が検証に使うCA証明書の束です。この forward-zone ブロックを丸ごと消せば、フルリゾルバーとして動きます。
このパッケージの版は起動時に warning: subnetcache: prefetch is set but not working for data originating from the subnet module cache. という警告を出します。サブネット情報付きの問い合わせ(ECS)を扱うモジュール向けの注意で、この構成の動作には影響しません。
起動とDNSSEC検証の確認
sudo unbound-checkconf
sudo systemctl restart unbound
dig @127.0.0.1 jprs.jp +dnssec | grep -E 'status|flags:'
dig @127.0.0.1 dnssec-failed.org | grep status
dig @127.0.0.1 dnssec-failed.org +cd +shortunbound-checkconf が no errors in /etc/unbound/unbound.conf を返してから再起動します。interface や port を変えたときは、reloadではなくrestartが必要です。検証中に interface を1行足して unbound-control reload したところ、応答は ok なのに新しいアドレスでは待ち受けておらず、digは connection refused になりました。
検証の結果は、digの flags: 行で読みます。署名付きの jprs.jp を引くと flags: qr rd ra ad と、検証済みを示す ad(Authenticated Data)が付きます。署名をわざと壊してある試験用ドメイン dnssec-failed.org は status: SERVFAIL になり、検証を無効にする +cd(Checking Disabled)を付けたときだけアドレスが返ります。この2つが揃えば、Unboundは改ざんされた応答を端末に渡さずに止めています。
署名には有効期間があるため、サーバーの時計が大きくずれていると、正しい署名まで期限切れと判定され、すべての名前がSERVFAILになります。RTC(時計用の電池)のないRaspberry Piは、起動直後にNTPで時刻が合うまでこの状態になりがちです。timedatectl で System clock synchronized: yes を確かめてから切り分けを始めてください。
LAN内の名前を返す:local-zoneの種類と使い分け
LAN内の機器に名前を付けるなら、ドメインは home.arpa を使います。家庭内ネットワーク用にRFC 8375で予約された名前で、インターネット上の名前と衝突しません。.local はmDNS(Bonjour)が使うので、ここで使うとmacOSやiPhoneで引けなくなることがあります。
| local-zoneの種類 | 動き | 使いどころ |
|---|---|---|
static | local-data に書いた名前だけ答え、それ以外はNXDOMAIN | home.arpa のような内部専用のゾーン |
transparent | local-data に書いた名前は答え、それ以外は通常どおり外へ問い合わせる | 公開ドメインの一部の名前だけLAN内のIPに向けたい場合 |
redirect | ゾーン配下のすべての名前に同じ答えを返す | ワイルドカードで1台のリバースプロキシに向ける |
always_nxdomain | 存在しない扱いにする | 特定ドメインの遮断 |
公開ドメイン example.com を持っていて、nas.home.example.com だけLANのアドレスに向けたい場合(スプリットDNS)は、local-zone: "home.example.com." transparent と local-data: "nas.home.example.com. IN A 192.168.10.20" を書き、あわせて private-domain: "home.example.com" を足します。広告ドメインの遮断も always_nxdomain で書けますが、リストの自動更新やクライアントごとの設定まで考えると、AdGuard Homeを前段に置いて役割を分ける方が管理が楽です。
クライアントから使う:ルーターのDHCPとsystemd-resolved
LAN全体で使うには、ルーターのDHCPサーバー設定でDNSサーバーのアドレスを 192.168.10.2 にします。端末はDHCPのリースを更新した時点で新しいDNSを使い始めます。VLANでネットワークを分けている場合は、各VLANのDHCPで同じアドレスを配り、access-control にそのVLANのサブネットを足します。VLAN間のファイアウォールで53番を通すことも忘れないでください(設計はVLANでネットワークを分離する手順を参照)。
サーバー自身の名前解決も、systemd-resolved経由でUnboundに向けます。
[Resolve]
DNS=127.0.0.1
Domains=~.sudo systemctl restart systemd-resolved
resolvectl status | grep -A2 'Global'広告ブロックも行いたい場合は、AdGuard Homeで家庭内の広告をDNSでブロックする構成の上位サーバーにUnboundを指定します。端末からの53番はAdGuard Homeが受け、Unboundは port: 5053 のように別のポートで待ち受けます。AdGuard HomeをDockerのブリッジネットワークで動かしている場合、コンテナから見た 127.0.0.1 はコンテナ自身なので、Unboundはホストのアドレス(例:192.168.10.2@5053)で待ち受けさせ、access-control にDockerのネットワーク(例:172.16.0.0/12)も加えます。
運用:統計とキャッシュ操作
sudo unbound-control stats_noreset | grep -E '^total.num.(queries|cachehits|cachemiss)='
sudo unbound-control list_forwards
sudo unbound-control flush www.example.com
sudo unbound-control flush_zone example.comstats_noreset はカウンターを消さずに統計を表示します(stats は表示と同時にゼロに戻します)。cachehits と cachemiss の比率を見れば、キャッシュがどの程度効いているかがわかります。list_forwards は転送先の一覧で、この記事の設定では . IN forward 1.1.1.1 1.0.0.1 と表示されます。Webサイトのサーバー移転直後に古いアドレスが返り続けるときは、flush で1つの名前を、flush_zone でドメイン配下をまとめてキャッシュから消します。
問い合わせの内容をすべて記録する log-queries: yes もありますが、家族全員の閲覧先がログに残ることになります。トラブルの調査中だけ有効にし、終わったら戻すのが無難です。
つまずきポイント
LAN内の端末からだけstatus: REFUSEDになる
サーバー上の dig @127.0.0.1 は通るのに、他の端末からはREFUSEDになる場合は、access-control にその端末のサブネットがありません。検証環境でも、許可していないアドレスから問い合わせると status: REFUSED が返りました。VLANを増やしたときに書き足し忘れるのが典型です。
起動しない:bind: address already in use
error: bind: address already in use と fatal error: could not open ports が出る場合は、同じアドレスの53番を別のプログラムが使っています。sudo ss -lunp 'sport = :53' で相手を確認します。dnsmasqやAdGuard Homeが同じサーバーで動いているか、Unboundに 0.0.0.0 を指定してsystemd-resolvedと衝突していることがほとんどです。
しばらく待たされたあとSERVFAILになる
DoTの転送先に届いていません。853番の外向き通信が塞がれていると、Unboundは転送先の応答を待ち続け、数十秒後にSERVFAILを返します。log-servfail: yes にしておくと、SERVFAIL <www.example.net. A IN>: all the configured stub or forward servers failed, at zone . from 192.0.2.1 upstream server timeout のように、どの転送先で失敗したかがログに出ます(到達できないアドレスを転送先にして再現した例です)。ルーターやUTMで853番を許可するか、forward-zone を外してフルリゾルバーにします。
動作確認
設定が意図どおり動いているかは、次の5つのコマンドでまとめて確かめられます。
$ unbound-checkconf
unbound-checkconf: no errors in /etc/unbound/unbound.conf
$ dig @127.0.0.1 jprs.jp +dnssec | grep -E 'status|flags:'
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51799
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
; EDNS: version: 0, flags: do; udp: 1232
$ dig @127.0.0.1 dnssec-failed.org | grep status
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 10216
$ dig @127.0.0.1 nas.home.arpa +short
192.168.10.20
$ sudo unbound-control stats_noreset | grep -E '^total.num.(queries|cachehits|cachemiss)='
total.num.queries=4
total.num.cachehits=1
total.num.cachemiss=3ad フラグ付きのNOERROR、試験用ドメインのSERVFAIL、home.arpa の答え、そして統計のカウンターが問い合わせのたびに増えることの4点が確認できれば完成です。EDNS の行の flags: do は、digが +dnssec で「署名も返してほしい」と要求したことを示すもので、検証の成否とは別の表示です。端末側では、Windowsなら nslookup nas.home.arpa、macOSやLinuxなら dig nas.home.arpa で、応答したサーバーが 192.168.10.2 になっているかも見ておきます。
よくある質問
BINDとUnboundの違いは何ですか?
BINDは権威DNSとキャッシュDNSの両方を1つのソフトで扱える多機能な実装で、Unboundはキャッシュ(フルリゾルバー)専用です。LANの名前解決だけなら、機能を絞ったUnboundの方が設定が短く、役割が混ざらないぶん安全に運用できます。自分のゾーンを外部に公開するなら、権威DNS側の製品を別に選びます。
Unboundの設定方法は?
/etc/unbound/unbound.conf.d/ に .conf ファイルを作り、server: の下に待ち受けアドレス(interface)と許可する送信元(access-control)を書くのが最低限です。DoTで転送するなら forward-zone を足し、unbound-checkconf で検査してから再起動します。
Unboundとはどういう意味ですか?
英語の unbound は「縛られていない」という意味の形容詞です。DNSの文脈では、オランダの非営利団体NLnet Labsが開発し、BSDライセンスで公開しているキャッシュDNSサーバーの名前を指します。日本語ではそのまま「アンバウンド」と読みます。
UbuntuでDNSの設定は?
Ubuntu 24.04では、systemd-resolvedが 127.0.0.53 で問い合わせを受けて実際のDNSサーバーへ転送します。DNSサーバーはnetplanの nameservers: か、resolved.conf.d/ の DNS= で指定し、resolvectl status で確認します。/etc/resolv.conf の直接編集は上書きされます。
まとめ
Unboundは、interface と access-control で受け付ける範囲を決め、DoTの forward-zone を付けるかどうかでプライバシーの考え方を選び、local-zone でLAN内の名前を足す、という3つの判断で組み上がります。DNSSECが効いているかは jprs.jp の ad フラグと dnssec-failed.org のSERVFAILで確かめられ、log-servfail を入れておけば障害時の切り分けも1行のログから始められます。DNSはLANのすべての通信の入口なので、この1台が止まったときに備え、DHCPで配るDNSを2台目に切り替える手順も決めておいてください。