0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BIND9によるDNSサーバーの構築

0
Last updated at Posted at 2025-12-29

0. DNSとは

DNS(Domain Name System)とは,ドメイン名とIPアドレスなどの情報の紐付けをするための仕組みのことです。コンピューターはドメイン名同士でインターネット通信しているのではなくドメイン名をIPアドレスに変換して通信をしています。コンピューターは2進法を使いますが,人間には分かりづらいのでIPv4は10進法で,IPv6は16進法で表記しているわけですが,いちいちIPアドレスを覚えていられないですね。そのためのDNSです。DNSという画期的なシステムがドメインとIPアドレスの紐付けをしてくれることで人間にとってとても分かりやすいドメイン名での通信ができるわけです。今回はこのDNSサーバーを建ててみようというお話です。

1. DNSレコードについて

DNSレコードとは,特定のドメインにおけるIPアドレスなどの情報を記録したデータです。例えばIPv4アドレスを記録するDNSレコードはAレコードになります。他にもAAAAレコードや,CNAMEレコード,TXT,DS,DNSKEY,HTTPSなど様々なレコードがあります。

・Aレコードについて

IPv4アドレスを記録しているDNSレコードです。DNSサーバーにAレコードを登録する時は,IPv4アドレスのみが指定できます。digコマンドでqiita.comIPv4アドレスを調べてみましょう。

実行結果
$ dig qiita.com. a
; <<>> DiG 9.20.17 <<>> qiita.com. A
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49356
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;qiita.com.			IN	A

;; ANSWER SECTION:
qiita.com.		37	IN	A	18.65.207.34
qiita.com.		37	IN	A	18.65.207.93
qiita.com.		37	IN	A	18.65.207.4
qiita.com.		37	IN	A	18.65.207.100

;; Query time: 16 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Thu Dec 25 09:17:28 JST 2025
;; MSG SIZE  rcvd: 102

このようにANSWER SECTION:にAレコードに割当されたIPv4アドレスが分かります。18.65.207.0/24から4つほどIPv4アドレスの割り当てをしているようです。

・AAAAレコードについて

AAAAレコードはIPv6アドレスを記録するDNSレコードです。AAAAレコードを登録する際はIPv6アドレスのみ登録できます。では,先ほどと同じようにdigqiita.comのAAAAレコードを調べてみましょう。

実行結果
$ dig qiita.com. aaaa
; <<>> DiG 9.20.17 <<>> qiita.com. AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59496
;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;qiita.com.			IN	AAAA

;; ANSWER SECTION:
qiita.com.		38	IN	AAAA	2600:9000:221b:1c00:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:4000:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:ba00:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:e400:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:7800:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:ea00:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:9c00:8:e1de:4d80:93a1
qiita.com.		38	IN	AAAA	2600:9000:221b:ae00:8:e1de:4d80:93a1

;; Query time: 12 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Thu Dec 25 09:23:16 JST 2025
;; MSG SIZE  rcvd: 262

このように表示されます。IPv6アドレスは128bitもあり,大量に有り余っているので,qiita.comには8個のIPv6アドレスの割り当てがされているようです。割り当てしているIPv6アドレスは前から49~64bit目の部分を変えているようですね。

・NSレコードについて

NSレコードは,ネームサーバーを記録するDNSレコードです。NSレコードを調べることでそのドメインの権威DNSサーバーを調べることができます。

権威DNSサーバーとは

特定のドメインのみのDNSレコードを管理するDNSサーバーで,他のDNSサーバーへの問い合わせをすることなくDNSレコードを返すことができます。とある特定のドメインの全てのDNSレコードの情報を管理しています。この権威DNSサーバーを指定して特定のドメインに割り当てされたレコードが正しいかどうかを調べることができます。

では権威DNSサーバーを調べてみましょう。

実行結果
$ dig qiita.com. ns
; <<>> DiG 9.20.17 <<>> qiita.com. NS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16842
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;qiita.com.			IN	NS

;; ANSWER SECTION:
qiita.com.		300	IN	NS	ns-1049.awsdns-03.org.
qiita.com.		300	IN	NS	ns-171.awsdns-21.com.
qiita.com.		300	IN	NS	ns-1956.awsdns-52.co.uk.
qiita.com.		300	IN	NS	ns-772.awsdns-32.net.

;; Query time: 108 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Thu Dec 25 09:34:54 JST 2025
;; MSG SIZE  rcvd: 175

複数のネームサーバーが割当てされています。ネームサーバーは原則最低2つ割当てしなければなりません。TLDとセカンドレベルドメインから判別すると1つはイギリスにあるネームサーバーを使用しているようです。ドメイン名にAWSが含まれているのでRoute53を使っていると予想ができます。では,このいずれかのドメインを利用してqiita.com.に割当てされているA,AAAAレコードを調べてみましょう。

実行結果
# Aレコードの確認
$ dig qiita.com. a @ns-1049.awsdns-03.org
; <<>> DiG 9.20.17 <<>> @ns-1049.awsdns-03.org qiita.com. a
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4353
;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 4, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;qiita.com.			IN	A

;; ANSWER SECTION:
qiita.com.		60	IN	A	18.65.207.34
qiita.com.		60	IN	A	18.65.207.93
qiita.com.		60	IN	A	18.65.207.100
qiita.com.		60	IN	A	18.65.207.4

;; AUTHORITY SECTION:
qiita.com.		300	IN	NS	ns-1049.awsdns-03.org.
qiita.com.		300	IN	NS	ns-171.awsdns-21.com.
qiita.com.		300	IN	NS	ns-1956.awsdns-52.co.uk.
qiita.com.		300	IN	NS	ns-772.awsdns-32.net.

;; Query time: 12 msec
;; SERVER: 2600:9000:5304:1900::1#53(ns-1049.awsdns-03.org) (UDP)
;; WHEN: Thu Dec 25 09:38:48 JST 2025
;; MSG SIZE  rcvd: 239

# AAAAレコードの確認
$ dig qiita.com. aaaa @ns-1049.awsdns-03.org
; <<>> DiG 9.20.17 <<>> @ns-1049.awsdns-03.org qiita.com. aaaa
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5815
;; flags: qr aa rd; QUERY: 1, ANSWER: 8, AUTHORITY: 4, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;qiita.com.			IN	AAAA

;; ANSWER SECTION:
qiita.com.		60	IN	AAAA	2600:9000:221b:200:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:1200:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:8600:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:a400:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:7a00:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:2e00:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:b400:8:e1de:4d80:93a1
qiita.com.		60	IN	AAAA	2600:9000:221b:a000:8:e1de:4d80:93a1

;; AUTHORITY SECTION:
qiita.com.		300	IN	NS	ns-1049.awsdns-03.org.
qiita.com.		300	IN	NS	ns-171.awsdns-21.com.
qiita.com.		300	IN	NS	ns-1956.awsdns-52.co.uk.
qiita.com.		300	IN	NS	ns-772.awsdns-32.net.

;; Query time: 18 msec
;; SERVER: 2600:9000:5304:1900::1#53(ns-1049.awsdns-03.org) (UDP)
;; WHEN: Thu Dec 25 09:39:03 JST 2025
;; MSG SIZE  rcvd: 399

ちゃんと1.1.1.1(Cloudflare DNS)と権威サーバーによるAレコードは同じですね。AAAAレコードは実際にもっとたくさん割当てされており一致はしませんでしたが,やはり49~64bitの部分以外は一緒なので表示されるIPv6アドレスは変わらないと言っても差し支えないでしょう。

2. DNSサーバーを構築するにあたって

DNSサーバーを構築するためにはDNSサーバーを構築するためのソフトウェアが必要です。今回はISC BIND9を使用します。結構昔から使われているDNSサーバーだと思います。digコマンドがBINDのツールですね。最近はnet-toolsからdig,iproute2への移行が推奨されているようです。今回はUbuntuでやりますが,他のディストリビューションでも同じようにできると思います。RHEL系のセキュリティ機能の強化で使用されるSELinuxについてはBIND9にどのような影響を与えるのかがまだ確かめたことがないので時間がある時にテストしてみます。

3. BIND9をインストール

まずはインストールしないと始まりません。インストールをしていきましょう。

パッケージのインストール
$ sudo apt-get install bind9 bind9-utils bind9utils

パッケージのインストールができたら設定を始めていきます。Linuxはデフォルトで使用されるリゾルバーがsystemd-resolvedパッケージによって設定されているので,BIND9を起動するのに邪魔なこのパッケージを削除します。

パッケージの削除
$ sudo apt-get purge systemd-resolved -y

設定を始めていきます。内部ネットワーク向けと外部ネットワーク向けでやり方が変わります。最初にまずは内部ネットワーク向けの設定からやってみましょう。

4.内部ネットワーク向け(リゾルバーの構築)

内部ネットワーク向けに構築するのでポート開放の必要はありません。ソフトウェアファイアウォールで内部ネットワーク向けに53/TCP,53/UDPを開放するだけでOKです。
ファイルの編集をしていきます。/etc/bind/内のnamed.conf.localはゾーンファイルの記入,named.conf.optionsはBIND9の挙動の指定を設定するファイルです。内部ネットワーク向けなのでnamed.conf.optionsだけ編集しておけばいいと思います。では編集していきましょう。

/etc/bind/named.conf.options
//ACLを追記
acl local-network { 192.168.0.0/16; 10.0.0.0/8; 172.16.0.0/12; fe80::/64; ::1; 127.0.0.0/8 };

options {
        directory "/var/cache/bind";
        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.
        // forwarders {};
        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation auto; 

        //追記
        recursion yes;
        allow-recursion { local-network; };
        
        listen-on-v6 port 53 { local-network; }; //listen-on-v6の後ろにport 53を追記
        listen-on port 53 { local-network; }; //追記
};

forwardersを設定すれば再起的名前解決をする際に使用するDNSサーバーの指定ができます。例えばCloudflare DNSを転送先として設定するならoptions内のforwardersのコメントアウトを外して,以下のように書けば良いです。

forwardersの設定
forwarders {
    1.1.1.1;
    1.0.0.1;
    2606:4700:4700::1111;
    2606:4700:4700::1001;
};

最後にBIND9を起動してリゾルバーの設定は完了になります。ポート開放をしていない場合はポート開放をする必要があるのでポート開放をしましょう。内部ネットワーク向けなのでルーターでのポート開放は原則不要です。

ソフトウェアファイアウォールによるポート開放

4-1.iptables,ip6tablesの場合

/etc/iptables/内のrules.v4rules.v6の編集をします。

/etc/iptables/rules.v4
~~~~~~~~~~~~
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [463:49013]
:InstanceServices - [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
#追記
-A INPUT -s 192.168.0.0/16 -p tcp --dport 53 -j ACCEPT
-A INPUT -s 172.16.0.0/12 -p tcp --dport 53 -j ACCEPT
-A INPUT -s 10.0.0.0/8 -p tcp --dport 53 -j ACCEPT
-A INPUT -s 192.168.0.0/16 -p udp --dport 53 -j ACCEPT
-A INPUT -s 172.16.0.0/12 -p udp --dport 53 -j ACCEPT
-A INPUT -s 10.0.0.0/8 -p udcp --dport 53 -j ACCEPT
~~~~~~~~~~~~
COMMIT
/etc/iptables/rules.v6
~~~~~~~~~~~~~~~~~~~~~~~
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
#追記
-A INPUT -s fe80::/64 -p tcp --dport 53 -j ACCEPT
-A INPUT -s fe80::/64 -p udp --dport 53 -j ACCEPT
~~~~~~~~~~~~~~~~~~~~~~~
COMMIT

最後に編集したルールの反映を行います。

ルールの反映
$ sudo iptables-restore < /etc/iptables/rules.v4
$ sudo ip6tables-restore < /etc/iptables/rules.v6

4-2. nftablesの場合(個人的に推奨)

nftコマンドが使えない場合はsudo apt-get install nftablesnftablesのインストールをしてください。
/etc/nftables.confを編集します。文法はかなり簡単で,iptablesと異なりIPv4/IPv6を同時に扱えるのでファイル編集の手間が減ります。また,とても高速かつ軽量なのでおすすめです。過去にnftablesの解説を少しだけしてます。

/etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority 0;

                policy drop;

                ct state { established, related } accept

                iifname "lo" accept
                ip6 nexthdr icmpv6 accept

                icmp type { echo-request, echo-reply } accept
                icmpv6 type { echo-request, echo-reply, nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert } accept

                #DNSポート開放
                ip saddr 192.168.0.0/16 tcp dport 53 accept
                ip saddr 172.16.0.0/12 tcp dport 53 accept
                ip saddr 10.0.0.0/8 tcp dport 53 accept
                ip saddr 192.168.0.0/16 udp dport 53 accept
                ip saddr 172.16.0.0/12 udp dport 53 accept
                ip saddr 10.0.0.0/8 udp dport 53 accept

                ip6 saddr fe80::/64 tcp dport 53 accept
                ip6 saddr fe80::/64 udp dport 53 accept
        }
        chain forward {
                type filter hook forward priority 0;
                policy drop;
        }
        chain output {
                type filter hook output priority 0;
                policy accept;
        }
}

構文に問題がないかチェックし,問題がなければnftablesを起動します。

構文チェック
$ sudo nft -cf /etc/nftables.conf       #何も問題なければ何も表示されない。
nftablesの起動
$ sudo systemctl enable --now nftables.service

4-3. UFWの場合

一番お手軽ですね。結局はiptables/ip6tables,ebtables,arptables,ufw,firewalldのバックエンドで動いているのはnftablesですからどれ使ってもいいんですが,nftablesで一元管理できるのでできれば移行すべきだと思います。
ではポート開放ですね。実はファイル編集すらいらないので超楽です。まずはUFWの起動から行います。

UFWの起動
$ sudo ufw enable

ではポート開放をしていきます。複数回コマンドを実行する必要があるので少々面倒かもしれません。

ポート開放の実行
$ sudo ufw allow from 192.168.0.0/16 to any port 53
$ sudo ufw allow from 172.16.0.0/12 to any port 53
$ sudo ufw allow from 10.0.0.0/8 to any port 53
$ sudo ufw allow from fe80::/64 to any port 53

ポート開放ができているか確かめるにはsudo ufw statusを実行します。

実行結果
$ sudo ufw status
To                         Action      From
--                         ------      ----
53                         ALLOW       192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, fe80::/64

このように表示されると思います。このように表示されればOKです。

4-4. 動作確認

ここまで設定をして実際にBIND9を起動したときに起動失敗したら悲しいので動作確認をちゃんとしましょう。

named.confなどの構文確認
$ sudo named-checkconf     #実行した際に何も表示されなければ問題ない

エラーなどが特に表示されなければ問題ないのでBIND9を再起動しましょう。この際にsystemctl reload named.serviceとしてはいけません。reloadではなくrestartにしてください。でないとエラーを吐き,起動エラーが出ます。もし,やってしまった場合はrestartに書き換えて再度実行し直してください。

BIND9サービスの再起動
$ sudo systemctl restart named.service #bind9.serviceでも良いがnamed.serviceの方が確実

問題なく再起動できたらちゃんと使えるか確かめましょう。diglocalhostを指定して正しく表示されればOKです。

動作確認
$ dig @localhost

; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @localhost
; (3 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36166
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: fb216f3ec603f2d1010000006951f577febe765d3f341fe2 (good)
;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			156888	IN	NS	c.root-servers.net.
.			156888	IN	NS	e.root-servers.net.
.			156888	IN	NS	l.root-servers.net.
.			156888	IN	NS	g.root-servers.net.
.			156888	IN	NS	h.root-servers.net.
.			156888	IN	NS	i.root-servers.net.
.			156888	IN	NS	b.root-servers.net.
.			156888	IN	NS	k.root-servers.net.
.			156888	IN	NS	a.root-servers.net.
.			156888	IN	NS	m.root-servers.net.
.			156888	IN	NS	d.root-servers.net.
.			156888	IN	NS	j.root-servers.net.
.			156888	IN	NS	f.root-servers.net.

;; ADDITIONAL SECTION:
a.root-servers.net.	156888	IN	AAAA	2001:503:ba3e::2:30
b.root-servers.net.	156888	IN	AAAA	2801:1b8:10::b
c.root-servers.net.	156888	IN	AAAA	2001:500:2::c
d.root-servers.net.	156888	IN	AAAA	2001:500:2d::d
e.root-servers.net.	156888	IN	AAAA	2001:500:a8::e
f.root-servers.net.	156888	IN	AAAA	2001:500:2f::f
g.root-servers.net.	156888	IN	AAAA	2001:500:12::d0d
h.root-servers.net.	156888	IN	AAAA	2001:500:1::53
i.root-servers.net.	156888	IN	AAAA	2001:7fe::53
j.root-servers.net.	156888	IN	AAAA	2001:503:c27::2:30
k.root-servers.net.	156888	IN	AAAA	2001:7fd::1
l.root-servers.net.	156888	IN	AAAA	2001:500:9f::42
m.root-servers.net.	156888	IN	AAAA	2001:dc3::35
a.root-servers.net.	156888	IN	A	198.41.0.4
b.root-servers.net.	156888	IN	A	170.247.170.2
c.root-servers.net.	156888	IN	A	192.33.4.12
d.root-servers.net.	156888	IN	A	199.7.91.13
e.root-servers.net.	156888	IN	A	192.203.230.10
f.root-servers.net.	156888	IN	A	192.5.5.241
g.root-servers.net.	156888	IN	A	192.112.36.4
h.root-servers.net.	156888	IN	A	198.97.190.53
i.root-servers.net.	156888	IN	A	192.36.148.17
j.root-servers.net.	156888	IN	A	192.58.128.30
k.root-servers.net.	156888	IN	A	193.0.14.129
l.root-servers.net.	156888	IN	A	199.7.83.42
m.root-servers.net.	156888	IN	A	202.12.27.33

;; Query time: 0 msec
;; SERVER: ::1#53(localhost) (UDP)
;; WHEN: Mon Dec 29 12:28:55 JST 2025
;; MSG SIZE  rcvd: 851

このように表示されれば問題なく動作しているでしょう。また同じネットワークにいる別のPCからもdigBIND9を動かしているIPアドレスを指定して動作するか確かめましょう。BIND9を動かしているPCのIPアドレスを192.168.1.254とします。

別環境から動作確認
$ dig @192.168.1.254
; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @192.168.1.254
; (3 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36166
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: fb216f3ec603f2d1010000006951f577febe765d3f341fe2 (good)
;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			156888	IN	NS	c.root-servers.net.
.			156888	IN	NS	e.root-servers.net.
.			156888	IN	NS	l.root-servers.net.
.			156888	IN	NS	g.root-servers.net.
.			156888	IN	NS	h.root-servers.net.
.			156888	IN	NS	i.root-servers.net.
.			156888	IN	NS	b.root-servers.net.
.			156888	IN	NS	k.root-servers.net.
.			156888	IN	NS	a.root-servers.net.
.			156888	IN	NS	m.root-servers.net.
.			156888	IN	NS	d.root-servers.net.
.			156888	IN	NS	j.root-servers.net.
.			156888	IN	NS	f.root-servers.net.

;; ADDITIONAL SECTION:
a.root-servers.net.	156888	IN	AAAA	2001:503:ba3e::2:30
b.root-servers.net.	156888	IN	AAAA	2801:1b8:10::b
c.root-servers.net.	156888	IN	AAAA	2001:500:2::c
d.root-servers.net.	156888	IN	AAAA	2001:500:2d::d
e.root-servers.net.	156888	IN	AAAA	2001:500:a8::e
f.root-servers.net.	156888	IN	AAAA	2001:500:2f::f
g.root-servers.net.	156888	IN	AAAA	2001:500:12::d0d
h.root-servers.net.	156888	IN	AAAA	2001:500:1::53
i.root-servers.net.	156888	IN	AAAA	2001:7fe::53
j.root-servers.net.	156888	IN	AAAA	2001:503:c27::2:30
k.root-servers.net.	156888	IN	AAAA	2001:7fd::1
l.root-servers.net.	156888	IN	AAAA	2001:500:9f::42
m.root-servers.net.	156888	IN	AAAA	2001:dc3::35
a.root-servers.net.	156888	IN	A	198.41.0.4
b.root-servers.net.	156888	IN	A	170.247.170.2
c.root-servers.net.	156888	IN	A	192.33.4.12
d.root-servers.net.	156888	IN	A	199.7.91.13
e.root-servers.net.	156888	IN	A	192.203.230.10
f.root-servers.net.	156888	IN	A	192.5.5.241
g.root-servers.net.	156888	IN	A	192.112.36.4
h.root-servers.net.	156888	IN	A	198.97.190.53
i.root-servers.net.	156888	IN	A	192.36.148.17
j.root-servers.net.	156888	IN	A	192.58.128.30
k.root-servers.net.	156888	IN	A	193.0.14.129
l.root-servers.net.	156888	IN	A	199.7.83.42
m.root-servers.net.	156888	IN	A	202.12.27.33

;; Query time: 0 msec
;; SERVER: 192.168.1.254#53(192.168.1.254) (UDP)
;; WHEN: Mon Dec 29 12:28:55 JST 2025
;; MSG SIZE  rcvd: 851

このように表示されれば問題なくBIND9が動いています。BIND9でアクセスできるWebサイトなどのフィルタリングなどもすることが可能なので,色々カスタマイズして使ってみると良いかもしれません。DNS経由でのフィルタリングは結構強力なのでそれでGoogle広告系統をブロックすればYouTubeの広告もブロックできるかもしれませんね。AdGuard Homeで自宅にAdGuard DNSサーバーを建てて広告ブロックをするみたいなこともできたりするらしいですが,時間あるときに試してみたいですね。

4-5. クライアント側の設定

Windowsであればネットワーク設定で接続しているWi-Fiやイーサネットの詳細からDNSサーバーの編集で,DNSサーバーを動かしているIPアドレス(例:192.168.1.9など)を指定すればシステムのデフォルトでそのDNSサーバーを使うようになります。
iOS/iPadOS/macOSの場合は,繋いでいるWi-Fiやイーサネットの詳細でDNSの構成という項目からDNSサーバーを追加します。その際にDNSサーバーを動かしているIPアドレスを追加すれば良いです。
Linuxなどの場合は/etc/resolv.confを編集すれば良いです。nameserverの後にIPアドレスを指定すればOKです。これはmacOS/BSD系でも同じようにできるはずです。ただ,macOSの場合は利き方が中途半端なのでこの方法ではなく上記の方法でやることをお勧めします。

/etc/resolv.conf
# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
# This file might be symlinked as /etc/resolv.conf. If you're looking at
# /etc/resolv.conf and seeing this text, you have followed the symlink.
#
# This is a dynamic resolv.conf file for connecting local clients directly to
# all known uplink DNS servers. This file lists all configured search domains.
#
# Third party programs should typically not access this file directly, but only
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
# different way, replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.
nameserver 1.1.1.1
nameserver 2606:4700:4700::1111

5.外部ネットワーク向け(権威DNSサーバーの構築)

オープンリゾルバーはあまりよろしくないので,あまり構築の仕方を書くつもりはないですが,上記の内部ネットワーク向けでnamed.conf.optionsallow-recursion { any; };に設定すればオープンリゾルバーになります。セキュリティを高めたいなら,sysctlでのカーネルパラメーターでSYNパケットの量の調節なりをすると良いかもしれません。
話が脱線しましたが,権威DNSサーバーの構築をやっていきましょう。まずは権威サーバーを建てるためにドメインが必要です。ドメインを所持していることを前提に話しますので,ドメインの取得などの話は割愛させていただきます。
権威サーバーを運用するにあたって最低限必要なDNSレコードは,SOA,NSの2種類とA,AAAAのいずれかの最低3種類です。NSレコードは2つ,SOAレコードは1つなので割り当ては最低3つすることになります。NSレコードを割り当てする際に指定するネームサーバーのドメインにA,AAAAレコードのいずれかでIPアドレスの割り当てが必要なので,累計4つ以上のレコードを必要とします。
グダグダ書いててても分かりづらいので実際に.confファイルの編集をしていきましょう。まずは,named.conf.optionsの設定からです。

/etc/bind/named.conf.options
acl local-network { ::1; 127.0.0.0/8; 192.168.0.0/16; 172.16.0.0/12; 10.0.0.0/8; fe80::/64; };

options {
        directory "/var/cache/bind";

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        // forwarders {
        //      0.0.0.0;
        // };

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation auto;

        // このように設定するか,もしくは recursion no;に設定する。
        // その場合はallow-recrsionの設定は無視される。
        recursion yes;
        allow-recursion { local-network; };

        //ポート番号は書かなくても良いが明示的に書いておくことを推奨する。
        listen-on port 53 { any; }; 
        listen-on-v6 port 53 { any; };
};

次に,named.conf.localを編集します。これは権威サーバーとして動かすドメイン名の記入とゾーンファイルの記入などを行います。

/etc/bind/named.conf.local
//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

// ""内に指定するドメインは管理したいドメインを入力する。
// 例えばssh.example.comをサブドメインとして管理したい場合は,""内に
// example.comと入力してゾーンファイルに書く際にサブドメインで設定する。
zone "example.com" {
    type master;
    file "/var/lib/bind/db.example.com;
    allow-update { none; };
};

次にゾーンファイルの設定をします。デフォルトではvar/lib内にはbindディレクトリが存在しないのでディレクトリを作成します。また,その際にパーミッションをchownbind:bindに設定し直します。

ディレクトリの作成と権限設定
$ sudo mkdir -p /var/lib/bind/
$ sudo chown -R bind:bind /var/lib/bind

これらの設定ができたら,fileに指定したパスにゾーンファイルを作成し記入していきます。以下はゾーンファイルの例です。参考にしてください。設定し終わったらゾーンファイルの所有者をbind:bindにすることを忘れないようにしましょう。

/var/lib/bind/db.example.com
$TTL 3600; 1hour, TTLTime To Liveの略
@   IN   SOA   ns1.example.com.   ns2.example.com. (
        1        ; Serial
        3600     ; Refresh
        1800     ; Retry
        2592000  ; Expire
        3600     ; Minium
)

;Name Server
@   IN   NS  ns1.example.com.
@   IN   NS  ns2.example.com.

;A Record
ns1   IN   A   23.75.13.8

;AAAA Record
ns2   IN   AAAA 2001:4865:4280:ad01:fa23::1
ゾーンファイルの権限設定
$ sudo chown bind:bind /var/lib/bind/db.example.com

#ゾーンファイルをtouch,viやnanoで作成・編集する際に
#sudo -u bindとして実行しているなら実行しなくても良い。

このように書くと良いでしょう。;の後ろにコメントとしてこのように書くとどのレコードを設定しているかの見分けが簡単になるのでオススメです。レコードの種類で区切るも良いですし,iCloudのカスタムメールのためのDNSレコード群のようにまとめるなどでも良いのでグループ化すると分かりやすいと思います。
NSレコード用のAレコードやAAAAレコードを割り当てする際は,53/TCP53/UDPを開いているIPアドレスの指定をするべきです。NSレコードの役割は権威DNSサーバーを指定することなので,権威DNSサーバーが53/UDP53/TCPが開いていないのは大問題です。なぜなら外部からの問い合わせによる名前解決ができないからです。
なので,最低限パケットフィルタリングの設定でこれらのアドレスの53/TCP,53/UDPは外部から通信が入れるようにする必要があります。フレッツ光クロス・フレッツ光ネクスト(IPoE)を使っている場合は設定が少々面倒です。MAP-E形式であればポート開放ができるので,この形式のIPv4 over IPv6が使えるコラボ光・プロバイダーを選ぶと良いと思います(例:OCN光,en光,BIGLOBE光,CyberBBなど)。DS-Lite方式でもポート開放ができないわけではないですがその場合はIPv4PPPoE方式も使用できる場合のみだった記憶があります。私はDS-Lite方式のプロバイダーを使用しない主義なのでDS-Lite方式の仕様はよく知らないのでこの場合はどうするべきかは分からないです(申し訳ないです)...。PPPoEの場合はルーターからポート開放の設定を簡単にできるので気にしなくて良いはずです。

・ゾーンファイルについて

権威サーバーにおけるゾーンファイルは,DNSレコードの付け足しなどをしたら,シリアル番号を増やした後にrndc reloadを実行するようにしてください。

ゾーンファイルのリロード
$ sudo rndc reload    #ゾーンファイルのSerialの番号を増やしてから実行する。

MAP-Eとは

RFC7597Mapping of Address and Port with Encapsulationの略で,IPv4パケットをカプセル化してIPv6パケット上で使えるようにする技術のことです。MAP方式には他にもMAP-Tと呼ばれるアドレス変換技術もありますが,日本ではMAP-Eが主流です。
フレッツNGNにおけるMAP-Eは1つのIPv4アドレスのポート(1024~65535)を240個単位でMAP-E使用をしているクライアントに分配するのでIPv4アドレスが固定されるので,IPv6アドレスでもポート開放(正しくはパケットフィルタリング)をすることができます。フレッツにおけるMAP-Eの場合はウェルノウンポートで外部に公開できないので,http://192.168.1.1:8888/tから「静的NAPT設定」と「IPv4パケットフィルタ設定」から別のポートへ変換する必要があります。ただし,これは固定IP契約をしていない場合の話なので固定IP契約している場合は使えるポートに制限がされることはありません。

5-1. BIND9の動作確認

named.conf.options,named.conf.localやゾーンファイルの記入ができたらまずは構文エラーがないかを確かめます。問題がなければプロセスを再起動します。パケットフィルタの設定ができたら名前解決できるか確かめます。その際は権威サーバーだけでなく外部のオープンリゾルバーやルーターのDNSからも確認できるかを確かめます。

構文チェック
$ sudo named-checkconf   #named.conf系のファイルの構文確認
$ sudo named-checkzone example.com. /var/lib/bind/db.example.com #ゾーンファイルの確認

問題がなければnamed-checkconfは何も表示されず,named-checkzoneはOKと表示されます。問題がないことがわかればプロセスを再起動します。

プロセスの再起動
$ sudo systemctl restart named.service # bind9.serviceでも良いがnamed.serviceの方が確実
$ sudo rndc reload #ゾーンファイルの読み直し。successfulと出れば問題なし

まずはローカルの権威サーバーで名前解決できるかを検証しましょう。ゾーンファイルに設定した内容が適用されているかを調べます。

動作確認(権威サーバー)
$ dig @192.168.1.254 ns1.example.com. a
; <<>> DiG 9.20.17 <<>> @192.168.1.254 ns1.example.com. a
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7014
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;ns1.example.com.			IN	A

;; ANSWER SECTION:
ns1.example.com.		3600	IN	A	23.75.13.8

;; Query time: 12 msec
;; SERVER: 192.168.1.254#53(192.168.1.254) (UDP)
;; WHEN: Mon Dec 29 13:51:02 JST 2025
;; MSG SIZE  rcvd: 72

このように表示されれば問題ありません。正常に設定できています。ここまでできたらパケットフィルタの設定をしていきましょう。

5-2. パケットフィルタリングやファイアウォールの設定

まずは,ソフトウェアファイアウォールの設定をしていきましょう。nftablesでの設定を紹介をします。iptablesufw,firewalldでも同じように設定できると思いますが紹介はしません。arptables,ebtables,firewalld,iptables/ip6tables,ufwのバックエンドで動作するのはnftablesなので初めからnftablesで設定できるように慣れておくと良いかもしれません。ufw,firewalldPythonスクリプトでarptables,ebtables,iptables/ip6tablesC/C++で書かれています。ですが同じくC/C++で書かれているnftablesの方が動作も速く軽量なので私は普段からnftablesを使用するようになりました。
では,nftablesのインストールからしていきましょう。デフォルトで入っているかもしれませんが,不安であればaptdpkgでインストールされているか確認するといいでしょう。

パッケージのインストール
$ sudo apt-get install nftables

デフォルトではnftablesは使用されないようになっているのでsystemdから自動起動するように設定をしましょう。

自動起動の設定
# --nowを付けることで自動起動を有効にするときについでに起動できる。
$ sudo systemctl enable --now nftables.service  

自動起動設定が有効にできたら/etc/nftables.confを編集していきます。私はIPアドレス単位でのフィルタリングをするのでiproute2IPv4,IPv6アドレスを確認してから設定しています。UbuntuはデフォルトでIPv6アドレスの一時アドレスを生成するモードが有効になっているのでこれを無効にした上でIPv6アドレスをわかりやすいように固定します。IPv6アドレスの一時アドレスの自動生成はカーネルパラメーターにより設定されているのでsysctlで無効にします。しなくても良いですが,しておいた方が後々楽かもしれません。個人的には推奨します。

/etc/sysctl.conf
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.tcp_syncookies=1
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.use_tempaddr =0
net.ipv6.conf.default.use_tempaddr =0
/etc/sysctl.d/10-ipv6-privacy.conf
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0

forwardfilterと書かれている部分のコメントアウトは外さなくても良いですが,tcp_syncookiesはコメントアウトを外しましょう。Syn Flood攻撃を軽減できます。デフォルトで今のLinuxは有効らしいですが明示的に有効にすると良いと思います。一番下のnet.ipv6.conf.all.use_tempaddr=0net.ipv6.conf.default.use_tempaddr=0Ubuntuでは2が標準なので0に設定し直しています。このようにすることでIPv6アドレスの一時アドレスが生成されなくなるのでややこしくなくなります。DebianRHEL系はデフォルトで0なんですけどね。
設定できたらsysctl -pで適用します。sysctl -pで即時に適用されますが念のため再起動を行います。

設定の適用と再起動
$ sudo sysctl -p
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0
$ reboot

設定ができたら次はIPv6アドレスの変更をします。デフォルトではMACアドレスが後半64bitになっているのでもっと分かりやすいアドレスに変えます。まずは割り当てされているIPv6 prefixの確認をしていきましょう。ifconfigは古いらしいのでipコマンドを使います。

IPv6アドレス(GUA)の確認
$ ip -6 a s scope global
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 240b:xxxx:xxxx:xxxx:146e:451a:a463:d059/64 scope global noprefixroute 

割り当てされている場合はこのように2から始まるIPv6アドレスが割り当てされているはずです。ですが,このアドレスは分かりづらいです。なのでアドレスを変更します。/64になっているので前半64bitは変えられませんから,後半の64bitを好きに変更します。私はよく240b:xxxx:xxxx:xxxx::1/64のようにしたりして分かりやすくしています。nmcliコマンドで変更していきます。この際にゲートウェイアドレスも設定しないとIPv6での通信ができなくなってしまうので気をつけましょう。ゲートウェイアドレスを調べましょう。

ゲートウェイアドレスを調べる
$ ip -6 neigh |grep router
fe80::1e7c:98ff:fec5:aabc dev eno1 lladdr 1c:7c:98:c5:aa:bc router REACHABLE 
240b:xxxx:xxxx:xxxx:1e7c:98ff:fec5:aabc dev eno1 lladdr 1c:7c:98:c5:aa:bc router REACHABLE 
fe80::1e7c:98ff:fec5:aabc dev ens2f0 lladdr 1c:7c:98:c5:aa:bc router STALE 
fe80::1e7c:98ff:fec5:aabc dev ens2f1 lladdr 1c:7c:98:c5:aa:bc router STALE 
240b:xxxx:xxxx:xxxx:1e7c:98ff:fec5:aabc dev ens2f0 lladdr 1c:7c:98:c5:aa:bc router STALE 
240b:xxxx:xxxx:xxxx:1e7c:98ff:fec5:aabc dev ens2f1 lladdr 1c:7c:98:c5:aa:bc router STALE 

このように表示されると思います。この場合のゲートウェイアドレスは2から始まるIPv6アドレスです。これをゲートウェイアドレスとして設定します。ではやっていきます。例ではネットワークインターフェース名をeno1としています。

nmcliでIPv6モードの変更
$ sudo nmcli con modify eno1 ipv6.method auto
$ sudo nmcli con modify eno1 ipv6.method manual
$ sudo nmcli con modify eno1 ipv6.gateway 240b:xxxx:xxxx:xxxx:xxxx:1e7c:98ff:fec5:aabc
$ sudo nmcli con modify eno1 ipv6.address 240b:xxxx:xxxx:xxxx::1/64
$ sudo nmcli con up eno1

最初にmanualとして実行しても良いんですがエラーが出ることがあるのでautoを実行してからmanualを実行することを推奨します。次にipv6.gatewayに上記で調べたゲートウェイアドレスを指定します。この際にCIDR表記する必要はありません。
次にipv6.addressですが,この部分を好きに変更できます。前半64bitはNTT/プロバイダーから割当されているものなので変更できませんが,後半は変更できるので自分にとって分かりやすいアドレスに変えると良いでしょう。アドレスを指定する際はCIDR表記で/64をつけることを推奨します。
設定ができたら,sudo nmcli con up eno1で適用します。再度ipコマンドで設定したようにできているか確認しましょう。

設定ができたか確認
$ ip -6 a s scope global
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 240b:xxxx:xxxx:xxxx::1/64 scope global noprefixroute

では,ここからやっとnftables.confの設定をします。では編集していきましょう。

/etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

table filter inet {
        chain input {
                type filter hook input priority 0;
                policy drop;

                ct state  { established, related } accept

                iifname "lo" accept
                ip6 nexthdr icmpv6 accept

                icmp type { echo-request, echo-reply } accept
                icmpv6 type { echo-request, echo-reply, nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert } accept

                #DNS
                ip daddr 192.168.1.254/24 tcp dport { 53, 443, 853 } accept
                ip daddr 192.168.1.254/24 udp dport 53 accept
                ip6 daddr 240b:xxxx:xxxx:xxxx::1 tcp dport { 53, 443, 853 } accept
                ip6 daddr 240b:xxxx:xxxx:xxxx::1 udp dport 53 accept

            }
        chain forward {
                type filter hook forward priority 0;
                policy drop;
            }
        chain output {
                type filter hook output priority 0;
                policy accept;
            }
}

設定ファイルが書けたら構文に問題がないかを確認します。問題があればどの部分が問題かを表示してくれます。問題なければ何も表示はされません。
設定でTCPポートに443と853がありますがこれはDNS over HTTPSDNS over TLSを設定するつもりがない場合は開く必要はありませんので,53だけで良いです。設定したい場合は開いておくと良いでしょう。

構文チェック
$ sudo nft -cf /etc/nftables.conf

問題がなかったらnftablesを再起動します。

プロセスの再起動
$ sudo systemctl restart nftables.service

ソフトウェアファイアウォールの設定は終わったので次はルーターでのパケットフィルタ設定をする必要があります。かなり面倒かもしれませんが,これができていないと外部から名前解決ができないので自前で権威サーバーの管理ができていることにはなりません。例としてNTTのレンタルルーターでのやり方を書くので,それ以外の場合は別途やり方を調べてください。おそらくはポート開放するだけだとは思います。

IPv6パケットフィルタリング

IPv6(IPoE)パケットフィルタリングの設定の方がIPv4でやるより簡単なはずなのでまずはそちらからやります。下記の画像のように宛先IPv6 Prefix/Prefix長の設定をします。240b:xxxx:xxxx:xxxx::1/64の場合はPrefixの方に240b:xxxx:xxxx:xxxx::1を,Prefix長の方に64を設定します。これらを設定するときは,右側の編集を押して編集します。UDPとTCPは同時に設定できないのでそれぞれ設定します。また,ポート番号毎にも設定を書く必要があります。53/TCPや53/UDPを選んで設定をするとdomainと表示されますが正常なので問題ありません。設定ができたらIPv6セキュリティレベルを高度にし,左側のチェックボックスにチェックを入れて,左下の設定を押して設定を適用します。これでIPv6の設定は完了です。下記の画像は設定例です。
スクリーンショット 2025-12-29 14.00.46.png

IPv4パケットフィルタリング

まずは, https://192.168.1.1:8888/tにアクセスします。以下のような表示になると思います。
スクリーンショット 2025-12-29 18.56.28.png
IPv4設定を選び,パケットフィルタリング設定をします。移動すると以下のような表示になると思います。
画像は固定IP契約時の場合の表示です。
スクリーンショット 2025-12-29 12.03.27.png
固定IPを契約していない場合は利用可能ポートが細かく合計240個書いてあると思います。こちらにアクセスして設定をしたことがない人は,まずは新しくユーザー名とアカウント名を決めて登録をする必要があります。ここの説明は割愛させてもらいます。アカウントにログインできたら,「IPv4パケットフィルタ設定」と「静的NAPT設定」を編集します。片方だけ設定しても外部からの通信は通さないので必ず両方設定してください。編集するときはNO.列の数字の部分をクリックすると編集することができます。送信元にはanyを,宛先にはBIND9を動かしているPCのIPv4アドレスを指定しましょう。方向はWAN=>LANか両方向を選んでください。
編集例は以下のようになります。
・IPv4パケットフィルタ設定の場合
スクリーンショット 2025-12-29 12.02.26.png
・静的NAPT設定の場合
スクリーンショット 2025-12-29 12.02.44.png
これらが設定できたらパケットフィルタの設定は完了です。外部からの問い合わせができるかを確かめましょう。

5-3.動作確認

あとは外部からの問い合わせに対応できるかを確かめます。問題がなければ正常にDNSクエリが返ってきます。問題があればSERVFAILとなって返ってきますので何か問題があることが分かります。確かめるときはオープンリゾルバー(Cloudflare DNS, Google DNS, Quad9 DNSなど)からの問い合わせと,ルーターのDNSサーバーからの問い合わせで確かめましょう。

オープンリゾルバーでの検証
$ dig @1.1.1.1 ns1.example.com. a  #Cloudflare DNSの場合
; <<>> DiG 9.20.17 <<>> @1.1.1.1 ns1.example.com. a
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7014
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;ns1.example.com.			IN	A

;; ANSWER SECTION:
ns1.example.com.		3600	IN	A	23.75.13.8

;; Query time: 12 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Dec 29 13:51:02 JST 2025
;; MSG SIZE  rcvd: 72

このように出れば正常に設定ができています。問題がある場合はstatus:の部分にSERVFAILのように表示されるはずです。Cloudflare DNSだけでなく他のGoogle DNSやルーターのDNSでも名前解決できるかを検証しましょう。

ルーターのDNSでの検証
$dig @192.168.1.1 ns1.example.com. a
; <<>> DiG 9.20.17 <<>> @192.168.1.1 ns1.example.com. a
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7014
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;ns1.example.com.			IN	A

;; ANSWER SECTION:
ns1.example.com.		3600	IN	A	23.75.13.8

;; Query time: 12 msec
;; SERVER: 192.168.1.1#53(192.168.1.1) (UDP)
;; WHEN: Mon Dec 29 13:51:02 JST 2025
;; MSG SIZE  rcvd: 72

問題がなければこのようになります。問題があれば同様にSERVFAILと表示されます。

オープンリゾルバー

Cloudflare DNSのIPアドレス

IPv4アドレス
1.1.1.1, 1.0.0.1 (何もフィルタリングなし,ドメイン名:one.one.one.one)
1.1.1.2, 1.0.0.2 (マルウェアのフィルタリングあり,ドメイン名:security.cloudflare-dns.com)
1.1.1.3, 1.0.0.3 (マルウェア・アダルトコンテンツのフィルタリングあり,ドメイン名:family.clouflare-dns.com)
IPv6アドレス
2606:4700:4700::1111,2606:4700:4700::1001 (何もフィルタリングなし,ドメイン名:one.one.one.one)
2606:4700:4700::1112,2606:4700:4700::1002 (マルウェアのフィルタリングあり,ドメイン名:security.cloudflare-dns.com)
2606:4700:4700::1113,2606:4700:4700::1003 (マルウェア・アダルトコンテンツのフィルタリングあり,ドメイン名:family.clouflare-dns.com)
2606:4700:4700::64,2606:4700:4700::6400 (DNS64,何もフィルタリングなし,ドメイン名:dns64.cloudflare-dns.com)

Google DNSのIPアドレス

IPv4アドレス
8.8.8.8,8.8.4.4 (何もフィルタリングなし,ドメイン名:dns.google)
IPv6アドレス
2001:4860:4860::8888,2001:4860:4860::8844 (何もフィルタリングなし,ドメイン名:dns.google)
2001:4860:4860::6464,2001:4860:4860::64 (DNS64,何もフィルタリングなし,ドメイン名:dns64.dns.google)

Quad9 DNSのIPアドレス

IPv4アドレス
9.9.9.9,149.112.112.9 (マルウェアのフィルタリングあり,ドメイン名:dns9.quad9.net)
IPv6アドレス
2620:fe::9,2620:fe::fe:9 (マルウェアのフィルタリングあり,ドメイン名:dns9.quad9.net)

他にもオープンリゾルバーはありますがこの記事で紹介するのは上記の3つです。マルウェアフィルタリングの強さはQuad9 > Cloudflare DNSのようです。アダルトコンテンツのフィルタリングが可能なのは大手のリゾルバーだとCloudflare DNSくらいかもしれません(他にもあるかもですが調べきれていないです...)。
オープンリゾルバーによる検証とルーターDNSによる検証が正常にできたら権威DNSサーバーの構築は完了です。あとはおまけでDNSSECの設定やDoH/DoTの設定の仕方について書こうと思います。DNSSECの設定については諸説あるので設定するかどうかはその人次第です。人によっては無駄だと言う人もいます。私は設定しようがしなかろうがリスクの差は変わらないと考えているので,それなら設定しておこうと思って設定しています。ただ,設定をミスするとGoogle DNSやCloudflare DNSでの名前解決ができなくなるので注意が必要です。

おまけ(DoH,DoT,DNSSECの設定)

DoHDNS over HTTPSの略で名前解決する際にHTTPSプロトコルを使うやり方です。HTTPSを使用するのでDNSクエリが暗号化されます。HTTPSパケットと混ざるのでWebへアクセスしているのか名前解決しているのかの判断を困難にします。クライアント側が使用するセキュリティ向上するための技術です。また,HTTPSパケットと混ざるので検閲回避に使用できる場合があります。
次にDoTDNS over TLSの略で名前解決する際にTLSプロトコルを使用しDNSクエリを暗号化するやり方です。デフォルトでは853/TCPを使うのでDoHと違って検閲回避に使用はできないです。DoHと違い,TLSプロトコルを使うので比較的シンプルです。
DNSSECDomain Name System Security Extentionsの略でDNSのセキュリティ拡張機能です。正直仕様が複雑で難しく説明できたもんじゃないですが,電子署名と公開鍵認証によるキャッシュポイズニング対策をするための機能だと思います。ただ,キャッシュポイズニング対策としては不完全と言われています。また他にもDNSSECが有効だとDNSクエリが肥大化するのでDDoS攻撃のリスク(リフレクション攻撃など)が上がりやすいです。また,他にもDNSSEC検証未対応なリゾルバーに対しての脆弱性があるとも言われています。現にDNSSECを設定していない企業もあり,AppleやAmazonなどの大企業のように設定していない企業もあります。Google DNSやCloudflare,Quad9,Akamaiなどは設定しています。一部インターネット関連の企業は設定していることが多いと思われます。
話が色々脱線した気がしますが,それぞれの設定の仕方について話していきます。DoHDoTに関しては設定ほぼ一緒なので同時に説明します。

DoH,DoTの設定

named.conf.optionsの編集もしますが,Let's Encryptなどから取得したTLS証明書が必要です。まずはTLS証明書を取得しましょう。もしくは自己証明書でも良いですがLet's Encryptなどから取得した証明書を利用することを推奨します。webrootモードでcertbotを使って証明書を取得しても良いですし(HTTP-01),RFC2136のDNS-01 Challengeで取得するようにしても良いです。RFC2136で設定するのは少々複雑ですのでこの記事では割愛させてください(今後記事編集した際に追記するかもしれないです...期待しないで...)。
NginxでHTTP-01 Challengeさせるので,まずはNginxCertbotをインストールします。Apache2でも良いですがここではApache2でのやり方は紹介しません。

パッケージのインストール
$ sudo apt-get install nginx certbot

インストールができたらNginxの設定をしていきましょう。証明書を取得するためのconfファイルを書いていきます。/etc/nginx/conf.dディレクトリ内に新しくconfファイルを設置しましょう。

/etc/nginx/conf.d/
$ cd /etc/nginx/conf.d/
$ sudo touch dns-cert.conf   #分かりやすい名称をつけておくことを推奨。
/etc/nginx/conf.d/dns-cert.conf
server {
    listen 80;
    listen [::]:80; #IPv6アドレスを使用する場合は追記
    server_name ns1.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    # その他のリクエストは拒否,あるいは適当なページを表示
    location / {
        return 404;
    }
}
構文チェック
$ sudo nginx -t   # test is successfulと表示されればOK
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Apache2が入って起動しておりNginxが起動できない場合はApache2を削除するか動きを止めましょう。下記のいずれかを実行しましょう。その後に再度Nginxを起動し直してみてください。

Apache2の停止
$ sudo systemctl disable --now apache2.service  #自動起動と動作の停止
$ sudo apt-get autoremove apache2               #パッケージの削除
Nginxの起動
$ sudo systemctl restart nginx.service #サービスの再起動
$ sudo systemctl enable nginx.service  #サービスの自動起動有効化

ではCertbotで証明書の取得をします。

証明書の取得
$ sudo certbot certonly --webroot -d ns1.example.com -d ns2.example.com -w /var/www/html

正常にいけばSuccessfullyと表示されるのでそれを使います。Crontabで2ヶ月毎に更新するように設定すれば良いでしょう(Let's Encryptの証明書の有効期限が90日のため)。この場合はおそらく/etc/letsencrypt/live/ns1.example.com/内にfullchain.pemprivkey.pemがあるのでこれを利用します。また,別途DH鍵をOpenSSLコマンドで作成します。時間が少々かかるので気長に待ちましょう。

DH鍵の作成
$ sudo openssl dhparam -out /var/lib/bind/dhparam.pem 3072

作成ができたらファイルのパーミッション設定などを行います。

権限の設定
$ sudo find /etc/letsencrypt/{live,archive} -type d -exec chmod 755 {} + #ディレクトリのモードを変更
$ sudo find /etc/letsencrypt/{live,archive} -type f -exec chmod 644 {} + #ファイルのモードを変更
$ sudo chown bind:bind /var/lib/bind/dhparam.pem #所有者をrootからbindへ変更

では,named.conf.optionsに追記をしていきます。

/etc/bind/named.conf.options
tls local-tls {
        key-file "/etc/letsencrypt/live/ns1.example.com/privkey.pem";
        cert-file "/etc/letsnencrypt/live/ns1.example.com/fullchain.pem";
        dhparam-file "/var/lib/bind/dhparam.pem";
};

http local {
    endpoints { "/dns-query"; };
};

acl local-network { ::1; 127.0.0.0/8; 192.168.0.0/16; 
                    172.16.0.0/12; 10.0.0.0/8; 
                    fe80::/64; 240b:xxxx:xxxx:xxxx::/64 };

options {
        directory "/var/cache/bind";

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        // forwarders {
        //      0.0.0.0;
        // };

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation auto;

        // このように設定するか,もしくは recursion no;に設定する。
        // その場合はallow-recrsionの設定は無視される。
        recursion yes;
        allow-recursion { local-network; };

        listen-on port 53 { any; }; 
        listen-on-v6 port 53 { any; };

        //下記は追記
        //DNS over HTTPS 443/tcp
        listen-on port 443 tls local-tls http local { any; };
        listen-on-v6 port 443 tls local-tls http local { any; };

        //DNS over TLS 853/tcp
        listen-on port 853 tls local-tls { any; };
        listen-on-v6 port 853 tls local-tls { any; };
};

設定ファイルに追記が終わったら構文チェックをし,問題がなければプロセスの再起動を行いましょう。

構文チェックと再起動
$ sudo named-checkconf                 #問題がなければ何も表示されない。
$ sudo systemctl restart named.service #bind9.serviceでも良いがnamed.serviceの方が確実。

プロセスが再起動できたら動作確認をしましょう。ポートはそれぞれDoHの場合は443/TCP,DoTの場合は853/TCPが開いていればOKです。上記で紹介したやり方でパケットフィルタの設定をしましょう。DoHの場合は+httpsを,DoTの場合は+tlsをオプションとして付け足して実行してください。問題なければ以下のような表示がされて,status: NOERRORとなるはずです。実際に他のドメインの名前解決ができるか,または権威サーバーで設定したなら運用しているドメインに対して別のクライアントからやってみてください。

検証
$ dig +https @192.168.1.254   #DoHの場合は+https, DoTの場合は+tls
; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @192.168.1.254 +https
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17366
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 3da8133d53c8b5fc01000000695279a559ea9f8532e660c8 (good)
;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			515827	IN	NS	h.root-servers.net.
.			515827	IN	NS	j.root-servers.net.
.			515827	IN	NS	e.root-servers.net.
.			515827	IN	NS	b.root-servers.net.
.			515827	IN	NS	m.root-servers.net.
.			515827	IN	NS	f.root-servers.net.
.			515827	IN	NS	d.root-servers.net.
.			515827	IN	NS	g.root-servers.net.
.			515827	IN	NS	a.root-servers.net.
.			515827	IN	NS	i.root-servers.net.
.			515827	IN	NS	c.root-servers.net.
.			515827	IN	NS	l.root-servers.net.
.			515827	IN	NS	k.root-servers.net.

;; ADDITIONAL SECTION:
a.root-servers.net.	515827	IN	AAAA	2001:503:ba3e::2:30
b.root-servers.net.	515827	IN	AAAA	2801:1b8:10::b
c.root-servers.net.	515827	IN	AAAA	2001:500:2::c
d.root-servers.net.	515827	IN	AAAA	2001:500:2d::d
e.root-servers.net.	515827	IN	AAAA	2001:500:a8::e
f.root-servers.net.	515827	IN	AAAA	2001:500:2f::f
g.root-servers.net.	515827	IN	AAAA	2001:500:12::d0d
h.root-servers.net.	515827	IN	AAAA	2001:500:1::53
i.root-servers.net.	515827	IN	AAAA	2001:7fe::53
j.root-servers.net.	515827	IN	AAAA	2001:503:c27::2:30
k.root-servers.net.	515827	IN	AAAA	2001:7fd::1
l.root-servers.net.	515827	IN	AAAA	2001:500:9f::42
m.root-servers.net.	515827	IN	AAAA	2001:dc3::35
a.root-servers.net.	515827	IN	A	198.41.0.4
b.root-servers.net.	515827	IN	A	170.247.170.2
c.root-servers.net.	515827	IN	A	192.33.4.12
d.root-servers.net.	515827	IN	A	199.7.91.13
e.root-servers.net.	515827	IN	A	192.203.230.10
f.root-servers.net.	515827	IN	A	192.5.5.241
g.root-servers.net.	515827	IN	A	192.112.36.4
h.root-servers.net.	515827	IN	A	198.97.190.53
i.root-servers.net.	515827	IN	A	192.36.148.17
j.root-servers.net.	515827	IN	A	192.58.128.30
k.root-servers.net.	515827	IN	A	193.0.14.129
l.root-servers.net.	515827	IN	A	199.7.83.42
m.root-servers.net.	515827	IN	A	202.12.27.33

;; Query time: 0 msec
;; SERVER: 192.168.1.254#443(192.168.1.254) (HTTPS)
;; WHEN: Mon Dec 29 21:52:53 JST 2025
;; MSG SIZE  rcvd: 851

下の方の;; SERVER:の欄にDoHの場合は#443とHTTPSが,DoTの場合は#853とTLSが表示されます。ちなみにオープンリゾルバーであるCloudflare DNS,Google DNS,Quad9などはDoTDoHの両方に対応しています。対応していないオープンリゾルバーもあるかもしれません。

DNSSECの設定

ゾーンファイルを編集した際に再度ZSKKSKで再度手動で署名するのは面倒なのでBIND9.9から実装されたインラインサイニングを使用します。インラインサイニングを利用することでBIND9が勝手に署名し直してくれるので私たちが直接署名し直す必要がないので楽です。ただ,DNSSECを設定するにはDNSSECの設定に対応したドメインレジストラを使う必要があります(Namecheap, Spaceshipなどが対応している)。GMO系列のレジストラ(お名前.comなど)のように特定のドメインしか対応していなく,尚且つ有料とかいうゴミカスレジストラもあるので注意してください。.jpドメインやxx.jpドメインなどを使用しない限りは基本はドメインの取得は海外レジストラに頼るべきです。海外レジストラは優しいので無料で全てのTLDに対してDNSSECを使わせてくれるのでおすすめです。私はNampcheapを使おうと思ったのですが.workドメインの移管に対応していないと言われたので対応しているSpaceshipを使ってくれと言われたのでそちらのレジストラを使わせてもらってます。移管元は~~某お⚪︎前.com~~です。なんで最初にここ使ったんだろう...?っていう後悔を未だにしてます。気を付けましょう。
話が脱線し過ぎたので,設定をしていきましょう。まずはnamed.conf.optionsを編集し追記をします。

/etc/bind/named.conf.options
~~~~~~~~~~~~~~~~~~~
options {
        directory "/var/cache/bind";
        key-directory "/var/lib/bind/key"; //追記
        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113
~~~~~~~~~~~~~~~~~~
};

では,ZSKKSKを保存するディレクトリの作成をしましょう。ディレクトリを作成する際は所有者がbind:bindになるようにします。

ディレクトリの作成
$ sudo -u bind mkdir -p /var/lib/bind/key

ディレクトリの作成ができたら,named.conf.localを編集します。

/etc/bind/named.conf.local
//必ず「zone "example.com"」の上の行に追記する。
dnssec-policy "ed25519-policy" {
    keys {
        ksk lifetime unlimited algorithm ed25519;
        zsk lifetime 60d algorithm ed25519;
    };
};

zone "example.com" {
        type master;
        inline-signing yes;
        dnssec-policy "ed25519-policy";
        file "/var/lib/bind/db.example.com";
        allow-update { none; };
};
viewで条件にマッチした場合のみ再帰的名前解決をさせる方法
acl trusted {
    192.168.0.0/16;
};

view "internal" {
    match-clients { "trusted"; };
    recursion yes;
    allow-recursion { any; };

    zone "example.com" {
        type master;
        file "/var/lib/bind/db.example.com";
    };
};

view "external" {
    match-clients { any; };
    recursion no; 
    zone "example.com" {
        type master;
        allow-update { none; };
        file "/var/lib/bind/db.example.com";
    };
};

viewで条件を分けても良いですが,設定が結構難しくなります。オープンリゾルバーと権威サーバーを同時に動かしたい場合は使うと良いかもしれません。試したことないので時間あるとき試してやり方を追記しときます。
設定ファイルの編集が終わったら構文チェックをし,問題なければプロセスを再起動します。

構文チェックと再起動
$ sudo named-checkconf
$ sudo systemctl restart named.service

プロセスの再起動が正常に完了したらレジストラにDSレコードの登録を行います。これを行わないとオープンリゾルバーでの名前解決時にBogusとなりstatus: SERVFAILと表示され名前解決が正常にできません。登録するDNSレコードは以下のようにして確認します。DSレコードの登録の仕方はレジストラによって違うので各自で調べてください。

DSレコードの確認
$ dig @localhost example.com. ds
; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> ds example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25251
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 4213d24a5e7680e001000000695288184a3f56087846c871 (good)
;; QUESTION SECTION:
;example.com.			IN	DS

;; ANSWER SECTION:
example.com.		86400	IN	DS	370 13 2 BE74359954660069D5C63D200C39F5603827D7DD02B56F120EE9F3A8 6764247C

;; Query time: 15 msec
;; SERVER: ::1#53(localhost) (UDP)
;; WHEN: Mon Dec 29 22:54:32 JST 2025
;; MSG SIZE  rcvd: 164

このように表示されると思います。この場合はキータグが370,アルゴリズムが13,ダイジェストタイプが2,その後の文字列がダイジェストです。ダイジェストが途中でスペースが入っていますがレジストラに登録する際はこのスペースを消してください。レジストラにDSレコードが登録できたらDNSが反映されるまで待ちましょう。基本はすぐに反映されることが多いですが,10分から1時間ほど置いてからdig +dnssec example.com. a @1.1.1.1などのように実行してstatus: SERVFAILではなくstatus: NOERRORと表示されるか確かめましょう。
インラインサイニングを有効にした後に,named.serviceを再起動すると新たにdb.example.com.signedなどのようなファイルが生成されますがこれはBIND9が自動で生成したものなので触らないようにしましょう。編集していいのはdb.example.comだけです。
また,ゾーンファイルを編集し,DNSレコードを追加した場合はシリアル番号を増やしてrndc reloadをする必要があります。ただ一々このコマンドを実行するのは面倒だと思うのでcrontabにスクリプトを実行させるようにする方が楽です。下記にスクリプトの例を載せとくので参考にしてください。もっといい書き方はあると思いますが私には最適化の仕方が分からないので諦めてます。動けばいいやって思ってるので。example.comになってるところとvar/lib/bind/db.example.comになってるところはそれぞれの環境にあったものに変更してください。

ゾーンファイル編集検知スクリプト(/var/lib/bind/zone-update.sh)
#!/bin/bash
TARGETS=(
    "example.com:/var/lib/bind/db.example.com"
)
STATE_DIR="/var/lib/bind/zone_watcher_state"
mkdir -p "$STATE_DIR"
chown bind:bind "$STATE_DIR" 2>/dev/null
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') [ZoneWatcher] $1"
}
for TARGET in "${TARGETS[@]}"; do
    ZONE_NAME="${TARGET%%:*}"
    STATE_FILE="$STATE_DIR/${ZONE_NAME}.hash"
    if [ ! -f "$ZONE_FILE" ]; then
        log "Error: Zone file for $ZONE_NAME not found at $ZONE_FILE"
        continue
    fi
    CURRENT_HASH=$(md5sum "$ZONE_FILE" | awk '{print $1}')
    if [ -f "$STATE_FILE" ]; then
        LAST_HASH=$(cat "$STATE_FILE")
    else
        LAST_HASH=""
    fi
    if [ "$CURRENT_HASH" != "$LAST_HASH" ]; then
        log "Change detected in $ZONE_NAME ($ZONE_FILE)."
        CURRENT_SERIAL_VAL=$(named-checkzone -t SOA -i local "$ZONE_NAME" "$ZONE_FILE" 2>/dev/null | grep "SOA" | grep -oE '[0-9]{10}' | head -1)
        
        if [ -n "$CURRENT_SERIAL_VAL" ]; then
            TODAY=$(date +%Y%m%d)
            SERIAL_DATE=${CURRENT_SERIAL_VAL:0:8}
            SERIAL_NUM=${CURRENT_SERIAL_VAL:8:2}
            
            if [ "$SERIAL_DATE" == "$TODAY" ]; then
                NEW_SERIAL="${TODAY}$(printf "%02d" $NEW_NUM)"
            else
                NEW_SERIAL="${TODAY}01"
            fi
            
            if [ "$CURRENT_SERIAL_VAL" != "$NEW_SERIAL" ]; then
                log "Auto-updating serial: $CURRENT_SERIAL_VAL -> $NEW_SERIAL"
                
                cp "$ZONE_FILE" "$ZONE_FILE.bak"
                
                MARKER_KEY="zone_watcher_id"
                
                if grep -q "; $MARKER_KEY:" "$ZONE_FILE"; then
                    sed -i "/; $MARKER_KEY:/s/[0-9]\{10\}/$NEW_SERIAL/" "$ZONE_FILE"
                    log "Updated serial using unique marker line."
                else
                    RAND_ID=$(date +%s%N | sha256sum | head -c 8)
                    MARKER="; $MARKER_KEY:$RAND_ID"
                    
                    sed -i "0,/$CURRENT_SERIAL_VAL/s//$NEW_SERIAL $MARKER/" "$ZONE_FILE"
                    log "Updated serial and added unique marker ($MARKER)."
                fi
                
                CURRENT_HASH=$(md5sum "$ZONE_FILE" | awk '{print $1}')
            else
                log "Serial is already up to date ($NEW_SERIAL)."
            fi
        else
            log "Warning: Could not parse current serial number. Skipping auto-update."
        fi
        CHECK_OUT=$(named-checkzone -i local "$ZONE_NAME" "$ZONE_FILE" 2>&1)
        
        if [ $? -eq 0 ]; then
            log "Syntax check OK. Reloading zone..."
            
            RNDC_OUT=$(rndc reload "$ZONE_NAME" 2>&1)
            
            if [ $? -eq 0 ]; then
                log "Reload successful: $RNDC_OUT"
                echo "$CURRENT_HASH" > "$STATE_FILE"
            else
                log "Error: rndc reload failed. $RNDC_OUT"
            fi
        else
            log "Error: Syntax check failed for $ZONE_NAME. Skipping reload."
            log "$CHECK_OUT"
        fi
    fi
done
実行権限の付与
$ sudo chmod +x /var/lib/bind/zone-update.sh

あとはrootcrontabに登録しとけばOKです。一度だけ手動で実行し,その後はcrontabに5分毎に実行するようにしておけば問題ないかと思います。仕組みとしてはゾーンファイルの編集を検知したらシリアル番号を増やしてrndc reloadを勝手に実行してDNSレコードの更新を反映させます。ゾーンファイルの編集を検知しなかった場合は動かないようになっています。リソースの無駄な消費を抑えるためですね。
では,crontabに登録していきます。crontabの書き方は調べたらいくらでも出てくるのでここで説明はしません。sudo crontab -e -u rootを実行します。

crontabの編集
0 5 1 */3 * certbot certonly --webroot -d ns1.example.com -d ns2.example.com --deploy-hook "systemctl restart named.service"
5 * * * * bash /var/lib/bind/zone-update.sh

1行目はTLS証明書の自動更新,2行目が上記のスクリプトの自動実行です。DoHDoTを設定している場合は1行目を設定しておくべきでしょう。間違いなく手作業で証明書更新するのは手間ですからね。DNSSECが正しく設定できているかは https://dnsviz.net/ から確認してみてください。問題があればどの部分が問題かを表示してくれます。問題がなければ問題ないと表示してくれます。

おわりに

DNSサーバーを自前で構築するのは大学の授業で初めてやったのですが,その時に面白いなと思って今でも自宅ではDNSサーバーの構築をしてます。流石にオープンリゾルバーは怖過ぎてやってられないですね。自宅ネットワーク内(フレッツの/56と192.168.1.0/24)だけオープンリゾルバーとして扱えるように設定してます。オープンリゾルバーの運用も今後はやってみたいですね。ただ,それは自宅のネットワークと隔離した環境でやりたいのでクラウドサービスあたりを借りてやるのがいいんでしょうかね?Oracle Cloud InfrastructureのArmインスタンスを使ってるんですが,これをオープンリゾルバーにしてみてもいいかもしれません。月10TBまで転送容量(Bandwith)が無料ですし,超えたとしてもネットワーク料金安いですからね。
私はドメインを3つ所有していてそのうちの2つはCloudflare CDNを通しているわけですが,そちらのDNSSECの対応はとても簡単だったんですよ。問題は自宅で運用してる方の権威DNSでのDNSSECの設定なんですよね。なんかマジでシリアル番号あたりの設定とかと絡むとDNSSECでの鍵連鎖が崩れるのか知りませんが,オープンリゾルバーでの名前解決時にエラーが発生したりしてめっちゃ大変でした。下手にゾーンファイルを触るのは良くないことだけは分かりましたね。あと,DNSSECって色々問題あってサブドメイン全て列挙される危険性があるそうです。DNS関連はまだまだ勉強中で知らないことも多くてこんなとんでもない問題もあるんだなぁって調べて知りました。あと実は過去にRFC2136BIND9を掛け合わせてDNS01-ChallengeによるLet's Encryptでワイルドカード証明書を取得しようとしたことがあるんですが全部ことごとく失敗したんですよね...。マジで難し過ぎる...。やる時は_acme-challenge.example.comっていうサブドメインを別のゾーンファイルで管理してLet's Encryptがこの別管理のゾーンファイルを操作できるようにすることで出来るようになるらしいんですが,なんか上手くできなかったんですよね。どんなエラーが出て無理だったかは忘れましたが。下手に自前の環境でやるよりも安定した確実なCloudflareなどに通してやった方が良いという知見だけは得れましたねw(じゃぁ何故こんな記事書いた...?)。でも,やりがいはあったので今後も色々なものを触ってみたいですね。コンテナー(Docker,Podman,K8Sなど)とかKVMとかを触ったことが全くなくてここら辺を勉強しないとなぁと思いつつ時間がなくて全然手を出せてないんですよね。Linuxだとコンテナーはカーネル上でネイティブ動作するのでパフォーマンスが良いことは知っているんですけど,WindowsだとHyper-V上で,macOSだとLinux VM上で動くらしいですからここが個人的には謎だなぁって思ってます。
今年最後の記事だと思います。実は1年以上編集放棄してる公開済みの記事の下書きがあってやる気が迷子なので数ヶ月は記事書かない気がしますw。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?