Unboundのインストールした時のメモ

はじめに

 Unbound1 をソースファイルからデプロイした際のメモをざっくりと書いていきます。もし、少しでも参考となれば嬉しいです。気付いたところがあれば都度更新予定です。

動作環境

今回は、手元のVMに Ubuntu 24.04 をインストールし、その上でUnboundをデプロイしています。
リソースは、1ソケット1コア、メモリ4GBです。
利用人数は、100人ほどを想定しています。過少に見積もっていますが、自宅で100人が使うことはないですし、もう少し設定値を上げればさらに多くの利用者を捌ける想定です。
メモリ4GBも Ubuntu の推奨要件を満たすためで、Raspberry Pi のような1GBでも安定して動作するようになっています。私は Raspberry Pi で動かしています。

構築

ディレクトリとユーザ作成

ファイルを配置するディレクトリの作成とUnboundを操作するユーザを作成します。
Docsにも以下のような記載があり、Unboundの管理するためにユーザを作成し、ログインシェルは"/usr/sbin/nologin"を指定します。

By default Unbound assumes that a user named unbound exists. You can add this user with an account management tool available on your system; on Linux this is usually useradd.
引用元:Installation — Unbound 1.24.2 documentation

### "-p"で再帰的にディレクトリを作成
$ sudo mkdir -p /var/unbound/{etc,dev,var/log,usr/local/etc/unbound,usr/local/sbin}

$ sudo groupadd unbound
$ sudo cat /etc/group
unbound:x:1001:

### "-g"で特定のグループに所属、"-d"でホームディレクトリ指定、"-s"でログインシェルを指定
$ sudo useradd -g unbound -d /var/unbound -s /usr/sbin/nologin unbound
$ sudo cat /etc/passwd
unbound:x:1001:1001::/var/unbound:/usr/sbin/nologin

パッケージをインストール

Docs2 に記載されている必要なパッケージをインストールします。

$ sudo apt install build-essential libssl-dev libevent-dev libexpat1-dev
$ sudo apt-get install bison flex

ソースファイルからデプロイ

$ cd /opt
$ sudo wget https://github.com/NLnetLabs/unbound/archive/refs/tags/release-1.24.2.tar.gz
$ sudo tar xzf release-1.24.2.tar.gz
$ cd unbound-release-1.24.2/

$ sudo ./configure --prefix=/var/unbound/usr/local \
  --with-chroot-dir=/var/unbound \
  --with-libevent

$ sudo make
$ sudo make install

$ sudo /var/unbound/usr/local/sbin/unbound -V
Version 1.24.2

Configure line: --prefix=/var/unbound/usr/local --with-chroot-dir=/var/unbound --with-libevent
Linked libs: libevent 2.1.12-stable (it uses epoll), OpenSSL 3.0.13 30 Jan 2024
Linked modules: dns64 respip validator iterator

BSD licensed, see LICENSE in source package for details.
Report bugs to unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues

デバイスファイル作成とユーザと所有者の変更

$ sudo mknod -m 644 /var/unbound/dev/null c 1 3
$ sudo mknod -m 644 /var/unbound/dev/random c 1 8
$ sudo mknod -m 644 /var/unbound/dev/urandom c 1 9
$ sudo chown -R unbound:unbound /var/unbound

systemd-resolvedの停止と自動起動の無効化

既存のサービスと重複するので、"systemd-resolved"は停止します。
Docs3では、インターネットへの接続が切断されないようにファイルを書き換える方法が記載されていますが、パッケージのインストールとソースファイルのダウンロードが完了しているので、インターネットへ接続できなくても問題ありません。時刻同期に失敗することが問題の場合は、やり方を考える必要がありそうです。
事前に時刻同期先を手元で動作しているNTPサーバ宛に変更することで、インターネットへ接続できなくても時刻同期が外れることはないかもしれません。

$ lsof -i:53
COMMAND   PID            USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
systemd-r 567 systemd-resolve   14u  IPv4   6649      0t0  UDP _localdnsstub:domain
systemd-r 567 systemd-resolve   15u  IPv4   6650      0t0  TCP _localdnsstub:domain (LISTEN)
systemd-r 567 systemd-resolve   16u  IPv4   6651      0t0  UDP _localdnsproxy:domain
systemd-r 567 systemd-resolve   17u  IPv4   6652      0t0  TCP _localdnsproxy:domain (LISTEN)

$ sudo systemctl stop systemd-resolved
$ sudo systemctl disable systemd-resolved
$ systemctl status systemd-resolved
<停止していることを確認>

$ lsof -i:53
<出力なし>

コンフィグファイルの編集

コメントアウトで記載している内容は補足程度で記載しています。設定値は手元の環境に合わせて変更します。

$ sudo cp /var/unbound/usr/local/etc/unbound/unbound.conf /var/unbound/etc/
$ sudo vi /var/unbound/etc/unbound.conf

server: 
    #出力するログの詳細度
    verbosity: 1

    #統計情報の取得間隔(0は起動から再起動まで)
    statistics-interval: 0
    #累積統計を有効にする(再起動した場合は統計情報はクリアされる)
    statistics-cumulative: yes

    #CPUのコア数(=Socket*Core)
    num-threads: 1

    #問い合わせを待ち受けるIPアドレス(手元の環境に合わせて変更)
    interface: 127.0.0.53
    interface: <listen ip address>

    #クエリを受けたIPアドレスから返す(DSRでLBを使っていたり特殊な構成の場合は"yes")
    interface-automatic: no

    #同時に保持できる外部への問い合わせるポート数("num-queries-per-thread"の2倍)
    outgoing-range: 2048

    #TCP接続数の最大値(DoT/DoHを利用する場合はもう少し多い方がいいかも)
    outgoing-num-tcp: 200
    incoming-num-tcp: 200

    #ネットワークのバッファサイズ(パケットドロップしないようにする受け皿)
    so-rcvbuf: 4m
    so-sndbuf: 4m

    #EDNS0で相手に伝達するUDP応答パケットサイズの最大値(1232でパケットドロップが発生する場合は1220にする)
    #1232バイト=1280バイト(IPv6の最小MTU) - 40バイト(IPv6ヘッダ) - 8バイト(UDPヘッダ)
    edns-buffer-size: 1232
    #クライアントへのUDP返信パケットサイズの最大値(edns-buffer-sizeと同値)
    max-udp-size: 1232

    #問い合わせの応答内容を処理できるサイズ(これより大きなサイズは破棄)
    msg-buffer-size: 65532

    #問い合わせに対して回答した内容を保持するサイズ(クライアントから問い合わせにヒットするものがあればキャッシュから返答)
    msg-cache-size: 64m

    #"num-threads"の値に近くなる2の累乗
    msg-cache-slabs: 2

    #1スレッドで保持する外部問い合わせ中リクエスト数("outgoing-range"の半分)
    num-queries-per-thread: 1024

    #データ(レコード)を保持
    rrset-cache-size: 128m
    #"num-threads"の値に近くなる2の累乗
    rrset-cache-slabs: 2

    #問い合わせに対して回答した内容を保持する時間の最大値(低くいと頻繁に外部へ問い合わせる)
    cache-max-ttl: 86400

    #"num-threads"の値に近くなる2の累乗
    infra-cache-slabs: 2

    #外部問い合わせにダウンしていなかったり応答速度が速いなど最適なホストを保持する数
    infra-cache-numhosts: 5000

    do-ip4: yes
    #IPv6を利用する環境であれば"yes"にする
    do-ip6: no
    do-udp: yes
    do-tcp: yes

    #外部への問い合わせを最初からTCPを利用する(FWなどでUDPがブロックされている場合は"yes")
    tcp-upstream: no

    #問い合わせを許可するセグメントを制限(127.0.0.53がないと自分からの名前解決ができない)
    access-control: 127.0.0.53/32 allow
    access-control: 192.168.1.0/24 allow
    access-control: 0.0.0.0/0 refuse

    #chrootのディレクトリを指定(環境に合わせて変更)
    chroot: "/var/unbound"
    #Unboundを管理するユーザ
    username: "unbound"
    #作業ディレクトリのパス(環境に合わせて変更)
    directory: "/var/unbound"

    #Unboundのログ格納場所(syslogを利用する場合は不要)
    logfile: "/var/unbound/var/log/unbound.log"
    #syslogを利用する(他のログと混在しないよう"no"にした方がいいかも)
    use-syslog: no
    #ログ出力にUTC-ASCIIタイムスタンプの形式を利用する(どっちでも)
    log-time-ascii: yes
    #ISO8601形式を利用する(どっちでも)
    log-time-iso: no
    #プロセスIDを記載する場所
    pidfile: "/var/unbound/etc/unbound.pid"

    #問い合わせの応答にバージョンや識別子を隠す(内部利用であれば気にしなくてもいいが、情報を隠す方が無難)
    hide-identity: yes
    hide-version: yes

    #クライアントからの極端に小さかったり大きいサイズで要求する問い合わせを拒否
    harden-short-bufsize: yes
    harden-large-queries: yes

    #権威DNSからの応答に含まれる信頼できない情報を無視(スプーフィング対策)
    harden-glue: yes

    #DNSSEC署名が存在すべきゾーンからの応答に署名がない場合は破棄
    harden-dnssec-stripped: yes

    #権威サーバからの応答で再帰的な名前解決の際に、正しい宛先であることを確認(キャッシュポイズニング対策)
    #問い合わせが増えるため負荷や応答速度が少し増加し、閲覧不可となった宛先があれば、"no"にする(セキュリティ強度の上げ過ぎ)
    harden-referral-path: yes

    #DNSSEC検証で情弱なアルゴリズムへのダウングレートを防止
    harden-algo-downgrade: yes

    #最小限の情報のみ上位DNSへ問い合わせる(プライバシー保護)
    qname-minimisation: yes

    #問い合わせる際に、ドメイン名の大文字・小文字をランダムに入れ替えて、正しい相手からの応答であることを確認する(RFC4343  キャッシュポイズニング対策)
    use-caps-for-id: yes

    #有効期限が残り10%以下で問い合わせが来た場合、バックグラウンドで更新する
    prefetch: yes
    #有効期限が残り10%以下で問い合わせが来た場合、DNSSECの鍵情報も同様にプリフェッチする
    prefetch-key: yes

    #問い合わせの応答からAUTHORITY, ADDITIONALセクションを省略して返す
    minimal-responses: yes

    #DNSSEC検証を有効にする
    module-config: "validator iterator"

    #DNSSECルートトラストアンカーファイルのパス
    auto-trust-anchor-file: "/var/unbound/usr/local/etc/unbound/root.key"

    #DNSSEC検証結果が失敗でも結果をクライアントに返答する(キャッシュポイズニング対策)
    val-permissive-mode: no

    #キャッシュが切れても暫定で古いキャッシュを返す
    serve-expired: no

    #DNSSEC公開鍵をメモリに保持できる容量
    key-cache-size: 64m
    #"num-threads"の値に近くなる2の累乗
    key-cache-slabs: 2
    #存在しないドメインやレコードが存在しない情報をメモリに保持できる容量
    neg-cache-size: 4m

#Unboundをコントローラで管理する設定(今回はローカルからの操作を想定)
remote-control: 
    control-enable: yes
    control-interface: 127.0.0.1 
    control-port: 8953

    server-key-file: "/var/unbound/etc/unbound_server.key"
    server-cert-file: "/var/unbound/etc/unbound_server.pem"
    control-key-file: "/var/unbound/etc/unbound_control.key"
    control-cert-file: "/var/unbound/etc/unbound_control.pem"

forward-zone:
        name: "."
        #アドレスはISPから周知されているDNSやpublicのDNSを指定
        forward-addr: <DNSのIPアドレス>

コントローラの証明書を作成

$ sudo /var/unbound/usr/local/sbin/unbound-control-setup -d /var/unbound/etc
setup in directory /var/unbound/etc
Certificate request self-signature ok
subject=CN = unbound-control
removing artifacts
Setup success. Certificates created. Enable in unbound.conf file to use

$ sudo chown -R unbound:unbound /var/unbound/etc

root.key (DNSSECルートトラストアンカー) の作成

$ sudo -u unbound /var/unbound/usr/local/sbin/unbound-anchor -a /var/unbound/usr/local/etc/unbound/root.key

confファイルの記法チェック

$ sudo /var/unbound/usr/local/sbin/unbound-checkconf /var/unbound/etc/unbound.conf
unbound-checkconf: no errors in /var/unbound/etc/unbound.conf

起動と停止

### "-c"でconfファイルを指定して起動
$ sudo /var/unbound/usr/local/sbin/unbound -c /var/unbound/etc/unbound.conf
$ sudo /var/unbound/usr/local/sbin/unbound-control -c /var/unbound/etc/unbound.conf status
version: 1.24.2
verbosity: 1
threads: 1
modules: 2 [ validator iterator ]
uptime: 17 seconds
options: reuseport control(ssl)
unbound (pid 10261) is running...

### "-c"でconfファイルを指定して停止
$ sudo /var/unbound/usr/local/sbin/unbound-control -c /var/unbound/etc/unbound.conf stop
ok

### エラーは unbound-control で操作するための 127.0.0.1:8953 へ接続できないためで、停止しているので接続できないのは問題なし
$ sudo /var/unbound/usr/local/sbin/unbound-control -c /var/unbound/etc/unbound.conf status
[1772258159] unbound-control[10292:0] error: connect: Connection refused for 127.0.0.1 port 8953
unbound is stopped

systemdの設定

ファイルの作成

systemdで起動する際のシェルスクリプトとサービスファイルを作成します。

$ sudo tee /var/unbound/usr/local/sbin/unbound_start.sh <<EOF
#!/bin/sh
/var/unbound/usr/local/sbin/unbound -c /var/unbound/etc/unbound.conf -d
EOF

$ sudo chmod +x /var/unbound/usr/local/sbin/unbound_start.sh
$ sudo chown unbound:unbound /var/unbound/usr/local/sbin/unbound_start.sh

### 内容が多いのでviコマンドなどで作成しても可
$ sudo tee /etc/systemd/system/unbound-chroot.service <<EOF
[Unit]
Description=Unbound DNS Server in chroot
After=network.target

[Service]
Type=simple
User=unbound
Group=unbound
DynamicUser=no
NoNewPrivileges=true
SupplementaryGroups=
ExecStartPre=/var/unbound/usr/local/sbin/unbound-checkconf /var/unbound/etc/unbound.conf
ExecStart=/var/unbound/usr/local/sbin/unbound_start.sh
ExecStop=/var/unbound/usr/local/sbin/unbound-control -c /var/unbound/etc/unbound.conf stop
Restart=always
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE CAP_SYS_CHROOT
AmbientCapabilities=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE CAP_SYS_CHROOT

[Install]
WantedBy=multi-user.target
EOF

ネットワークバッファ制限値の変更

今回の想定している利用規模では、変更する必要はありませんが、Unboundの起動時に警告が出るため変更しています。出力される警告は動作に影響はありませんので、気にならない方や余計な設定変更はしたくない場合、変更は不要です。
規模が大きい場合は変更し、さらに大きい場合は、"8388608(8MB)"またはそれ以上にすることを検討します。

$ sudo vi /etc/sysctl.conf
+ net.core.rmem_max = 4194304
+ net.core.wmem_max = 4194304

$ sudo sysctl -p
net.core.rmem_max = 4194304
net.core.wmem_max = 4194304

起動

$ sudo systemctl daemon-reload
$ sudo systemctl enable unbound-chroot.service
$ sudo systemctl start unbound-chroot.service
$ sudo systemctl status unbound-chroot.service
● unbound-chroot.service - Unbound DNS Server in chroot
     Loaded: loaded (/etc/systemd/system/unbound-chroot.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-02-28 06:29:56 UTC; 12min ago
    Process: 10761 ExecStartPre=/var/unbound/usr/local/sbin/unbound-checkconf /var/unbound/etc/unbound.conf (code=exited, status=0/SUCCESS)
   Main PID: 10763 (unbound_start.s)
      Tasks: 2 (limit: 4603)
     Memory: 87.7M (peak: 87.9M)
        CPU: 1.165s
     CGroup: /system.slice/unbound-chroot.service
             tq10763 /bin/sh /var/unbound/usr/local/sbin/unbound_start.sh
             mq10764 /var/unbound/usr/local/sbin/unbound -c /var/unbound/etc/unbound.conf -d

Feb 28 06:29:56 <hostname> systemd[1]: Starting unbound-chroot.service - Unbound DNS Server in chroot...
Feb 28 06:29:56 <hostname> unbound-checkconf[10761]: unbound-checkconf: no errors in /var/unbound/etc/unbound.conf
Feb 28 06:29:56 <hostname> systemd[1]: Started unbound-chroot.service - Unbound DNS Server in chroot.

### ログで起動を確認
$ sudo tail /var/unbound/var/log/unbound.log
Feb 28 06:29:56 unbound[10664:0] notice: init module 0: validator
Feb 28 06:29:56 unbound[10664:0] notice: init module 1: iterator
Feb 28 06:29:56 unbound[10664:0] info: start of service (unbound 1.24.2).

状態確認

プロセス確認

systemdで起動するようにした際に、起動スクリプトにバックグラウンドで起動するように記載したので、起動シェルスクリプトとバックグラウンド起動の二つが出力されます。

$ ps aux | grep unbound
unbound   15985  0.0  0.0   2328   832 ?      Ss   Feb28   0:00 /bin/sh /var/unbound/usr/local/sbin/unbound_start.sh
unbound   15986  0.0  2.8  11032  19300 ?     S    Feb28   0:50 /var/unbound/usr/local/sbin/unbound -c /var/unbound/etc/unbound.conf -d

chroot確認

PIDは、確認した起動プロセスを指定します。 chrootで指定したディレクトリが表示されることを確認する。

$ sudo ls -l /proc/<PID>/root
lrwxrwxrwx 1 unbound unbound 0 Feb 28 06:51 /proc/15986/root -> /var/unbound

DNSSEC動作確認

失敗する場合は、以下のような結果となります。"status"が「SERVFAIL」となります。
最初の数回ほどタイムアウトする原因はわかりません。単純に時間がかかっているのかも。

$ sudo dig @127.0.0.53 +dnssec sigfail.verteiltesysteme.net
;; communications error to 127.0.0.53#53: timed out

; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @127.0.0.53 +dnssec sigfail.verteiltesysteme.net
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 32565
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;sigfail.verteiltesysteme.net.  IN      A

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sat Feb 28 06:10:40 UTC 2026
;; MSG SIZE  rcvd: 57

成功する場合は、失敗の際と異なり、"status"が「NOERROR」となります。
また、"flags"に「ad」が表示され、"ANSWER SECTION"にはRRSIGレコードが返されます。

$ sudo dig @127.0.0.53 +dnssec sigok.verteiltesysteme.net
;; communications error to 127.0.0.53#53: timed out

; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @127.0.0.53 +dnssec sigok.verteiltesysteme.net
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47080
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;sigok.verteiltesysteme.net.    IN      A

;; ANSWER SECTION:
sigok.verteiltesysteme.net. 1795 IN     CNAME   sigok.rsa2048-sha256.ippacket.stream.
sigok.verteiltesysteme.net. 1795 IN     RRSIG   CNAME 13 3 1799 202603001000000 20260210000000 46187 verteiltesysteme.net. **********************************
sigok.rsa2048-sha256.ippacket.stream. 59 IN A   123.234.56.36
sigok.rsa2048-sha256.ippacket.stream. 59 IN RRSIG A 8 4 60 202603001040000 20260131040002 46460 rsa2048-sha256.ippacket.stream. ************************************

;; Query time: 245 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sat Feb 28 06:10:24 UTC 2026
;; MSG SIZE  rcvd: 555

性能確認

以下のコマンドを実行し、統計情報を出力します。

$ sudo /var/unbound/usr/local/sbin/unbound-control -c /var/unbound/etc/unbound.conf stats_noreset

unbound.confで設定した制限に抵触した通信が増えている場合は、起動直後であれば何らかの不具合が生じていると見るべきです。

total.num.queries_ip_ratelimited=0  #攻撃(怪しい問い合わせ)対策に抵触した問い合わせの数

どの程度のキャッシュがヒットしているかなどを確認できます。

  • 問い合わせ数:728(total.num.queries) = 404(total.num.cachehits) + 324(total.num.cachemiss)

  • キャッシュヒット率:404(total.num.cachehits) / 728(total.num.queries) * 100 = 55%

  • 外部への問い合わせ数:324(total.num.cachemiss) = 322(total.num.recursivereplies) + 2(total.num.queries_discard_timeout)

total.num.queries=728  #DNSに来た問い合わせの総数
total.num.cachehits=404  #キャッシュにヒットした数
total.num.cachemiss=324  #キャッシュにヒットしなかった数(=外部への問い合わせた数)
total.num.recursivereplies=322  #外部への問い合わせからの応答数
total.num.queries_discard_timeout=2  #タイムアウトにより破棄された問い合わせの数

"total.requestlist.max"は"num-queries-per-thread"で指定した値の1/3までは安全、半分を超えると注意が必要かも。

total.requestlist.max=13  #同時処理した最大問い合わせの数
total.requestlist.overwritten=0 #処理しきれずに破棄した問い合わせの数
total.requestlist.exceeded=0 #処理しきれずに破棄した問い合わせの数

平均値が中央値の10倍を超えると一部の問い合わせに時間がかかっているかタイムアウトの可能性があるので、"forward-addr"の宛先DNSを変更してみたり、"serve-expired"を"yes"にしてみる。動作と体感に大きな問題がなければ無視してもいいと思います。

total.recursion.time.avg=0.021824  #キャッシュにヒットせず外部への問い合わせからの応答速度の平均値(s)
total.recursion.time.median=0.0064  #キャッシュにヒットせず外部への問い合わせからの応答速度の中央値(s)

参照

  1. Unbound by NLnet Labs — Unbound 1.24.2 documentation
  2. 日本Unboundユーザー会
  3. 最適化の方法 - 日本Unboundユーザー会
  4. Howto Optimise - Unbound
  5. Unboundの紹介とその運用
  6. DNSキャッシュサーバ 設計と運用のノウハウ
  7. キャッシュDNSのDNSSEC対応
  8. DNSのRFCの歩き⽅方