2026年6月7日

2026年6月7日

DoH (DNS over HTTPS) を導入してセキュリティを強化する設定|Unbound で DoH を受け、送信元・レート・ログを絞って公開する

社内や拠点の端末の名前解決を DoH(DNS over HTTPS)で暗号化したい場合、Unbound に直接 DoH を受けさせると、リゾルバー1つで構成できます。ただし、DoH の入り口を作るだけではセキュリティは強くなりません。使える相手を絞る、問い合わせの量を制限する、DNSSEC で応答を検証する、ログに残す内容を決める、までを設定して初めて「強化」になります。この記事では、Unbound での DoH の待ち受け設定と、公開前に入れておきたい制限の設定を、確認方法とあわせて説明します。

ブラウザや Windows で公開の DoH サービスを使う設定は別の記事で扱っています。ここは、自分で DoH のリゾルバーを用意する運用者向けです。

DoH で強化できること・できないこと

最初に範囲をはっきりさせておくと、設定の優先順位を決めやすくなります。

  • 強化できること。 端末からリゾルバーまでの経路で、問い合わせの盗み見と書き換えを防げます。公衆無線 LAN や拠点間の回線など、途中のネットワークを信頼しにくい場面で効果があります。
  • 強化できないこと。 リゾルバーから権威 DNS サーバーへの問い合わせは、通常は平文のままです。接続先のホスト名は TLS の SNI などから見える場合があります。また、問い合わせの内容はすべてリゾルバーの運用者、つまり自分たちに集まります。

経路の改ざんへの対策は DoH が、応答そのものが正しいかの確認は DNSSEC の検証が担います。両方を有効にしておくのが基本です。

構成を決める

Unbound の DoH は、TLS を自分で終端し、HTTP/2 で待ち受けます。ここから次の制約が出てきます。

  • 443 番を Web サーバーと共有できない。 同じ IP アドレスで nginx や Apache が 443 番を使っていると、Unbound は待ち受けられません。WordPress を動かしているサーバーに同居させるなら、DoH 用に別の IP アドレスを割り当てるか、別のサーバーに分けます。
  • 前段に一般的なリバースプロキシを置きにくい。 プロキシから上流への接続が HTTP/1.1 だと、Unbound の DoH には渡せません。プロキシを挟まずに Unbound へ直接つなぐ構成が、この記事の前提です。

使う人の範囲も決めます。誰でも使える公開リゾルバーにすると、負荷対策と悪用対策、ログの扱いの説明まで責任を持つことになります。社内や拠点など、送信元のネットワークが決まっている用途に絞るのが現実的です。

事前に用意するもの

  • nghttp2 付きでビルドされた Unbound。 ディストリビューションのパッケージでは DoH が無効なことがあります。unbound -V の出力に nghttp2 を有効にしたビルドオプションが含まれているかを確かめます。
  • DoH 用のホスト名と証明書。 例として dns.example.com を使います。利用者の端末が証明書を検証するため、公的な CA の証明書か、端末に配布済みの社内 CA の証明書を用意します。そのサーバーで 80 番を使えない場合は、DNS-01 チャレンジで取得する方法があります。
  • バックアップ。 既存の Unbound を使っているなら、/etc/unbound/ 以下をコピーしてから作業します。可能ならステージング用のサーバーで先に試します。

Unbound の設定

設定ファイルを分けて書くと、元の設定と区別しやすくなります。アドレスとネットワークは例なので、自分の環境に合わせて置き換えてください。

# /etc/unbound/unbound.conf.d/doh.conf(例)
server:
    # 待ち受け:DoH 用のアドレスの 443 番と、サーバー自身用の 53 番
    interface: 203.0.113.53@443
    interface: 127.0.0.1@53
    https-port: 443
    http-endpoint: "/dns-query"
    tls-service-key: "/etc/unbound/tls/privkey.pem"
    tls-service-pem: "/etc/unbound/tls/fullchain.pem"

    # 使える相手を絞る(記載のない送信元は拒否)
    access-control: 0.0.0.0/0 refuse
    access-control: ::0/0 refuse
    access-control: 127.0.0.0/8 allow
    access-control: 198.51.100.0/24 allow

    # 送信元ごとの問い合わせ量の上限(1秒あたり)
    ip-ratelimit: 50

    # HTTP/2 の同時ストリーム数の上限
    http-max-streams: 100

    # 情報を出しすぎない
    hide-identity: yes
    hide-version: yes
    qname-minimisation: yes

    # ログは最小限にする
    verbosity: 1
    log-queries: no
    log-replies: no

設定のポイントは次のとおりです。

  • **https-port と interface の組み合わせ。** interface に書いたアドレスのうち、ポート番号が https-port と同じものが DoH の待ち受けになります。53 番はループバックだけにして、外部に平文の DNS を出さないようにします。
  • **access-control。** Unbound の既定はループバック以外を拒否する動きですが、意図を明示するために拒否と許可の両方を書いています。許可する範囲は、実際に使う拠点や VPN のアドレスに限ります。
  • **ip-ratelimit。** 1つの送信元からの問い合わせが1秒あたりの上限を超えると、超えた分の応答を制限します。値は例なので、拠点の端末数や NAT の有無を見て決めます。1つのグローバル IP の後ろに多くの端末がいる拠点では、低すぎる値だと正常な利用まで止めてしまいます。
  • DNSSEC。 ディストリビューションのパッケージでは、auto-trust-anchor-file による検証が既定で有効になっていることが多いです。別の設定ファイルに書かれていることもあるため、unbound-checkconf -o auto-trust-anchor-file で値を確かめます。

各オプションの既定値と細かな動きは Unbound の版で変わることがあるため、使っている版の unbound.conf のマニュアルで確認してください(2026年10月現在)。

証明書ファイルの権限

秘密鍵は Unbound の実行ユーザーだけが読めるようにします。certbot の保存先を直接指定せず、/etc/unbound/tls/ にコピーして権限を付けると、更新時の扱いもそろえやすくなります。

sudo install -d -m 0750 -o root -g unbound /etc/unbound/tls
sudo install -m 0640 -o root -g unbound /etc/letsencrypt/live/dns.example.com/fullchain.pem /etc/unbound/tls/
sudo install -m 0640 -o root -g unbound /etc/letsencrypt/live/dns.example.com/privkey.pem   /etc/unbound/tls/

グループ名 unbound はディストリビューションで異なることがあります。証明書を更新したときに同じコピーと再起動を行うよう、certbot の deploy フックにも登録しておきます。

ファイアウォールで二重に絞る

Unbound の access-control に加えて、ファイアウォールでも送信元を絞ります。片方の設定を誤っても、もう片方で防げるようにするためです。ufw の例です。

sudo ufw allow from 198.51.100.0/24 to 203.0.113.53 port 443 proto tcp
sudo ufw status numbered

53 番を外部に開けていないことも、ufw status で確かめておきます。

反映と動作確認

sudo unbound-checkconf
sudo systemctl restart unbound
sudo ss -ltnp | grep unbound

ss の出力で、203.0.113.53:443 と 127.0.0.1:53 で待ち受けていることを確かめます。続いて、許可したネットワークの端末から DoH で問い合わせます。

# RFC 8484 の例にある問い合わせを GET で送る(200 と application/dns-message が返るか)
curl -sS -D - -o /dev/null -H 'accept: application/dns-message' \
  'https://dns.example.com/dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB'

# DNS としての応答と DNSSEC の検証(flags に ad が付くか)
dig @dns.example.com +https +dnssec isc.org A

最後に、許可していない外部の端末からも同じ問い合わせを送り、接続できないか、REFUSED が返ることを確かめます。ここで応答が返るなら、どこかの制限が効いていません。

ログとプライバシーを決める

DoH のリゾルバーには、利用者がどのサイトを見ようとしたかが集まります。暗号化で経路を守っても、リゾルバーに詳細なログが無期限に残っていれば、そこが新しい弱点になります。

  • 通常時は log-queries と log-replies を無効にし、障害調査のときだけ期間を決めて有効にします。
  • 統計が必要なら、unbound-control stats_noreset のような集計値で足りないかを先に検討します。
  • 社外の人にも使わせる場合は、何をどれだけの期間残すかをプライバシーポリシーなどで示します。

端末に配る

利用者には、URL(https://dns.example.com/dns-query)と、必要に応じて IP アドレスを案内します。Firefox ではカスタムの DoH プロバイダーとして URL を指定できます。Windows 11 では PowerShell で IP アドレスと URL の組み合わせを登録し、その IP アドレスを DNS サーバーに設定すると OS の DoH が使えます。

Add-DnsClientDohServerAddress -ServerAddress 203.0.113.53 `
  -DohTemplate https://dns.example.com/dns-query `
  -AllowFallbackToUdp $false -AutoUpgrade $true

-AllowFallbackToUdp $false は、DoH が使えないときに平文へ戻さない設定です。暗号化を優先する代わりに、リゾルバーが止まると名前解決もできなくなるため、予備のリゾルバーを用意してから使います。

まとめ

  • DoH で守れるのは端末とリゾルバーの間です。応答の正しさは DNSSEC の検証で確かめます。
  • Unbound の DoH は TLS を自分で終端するため、Web サーバーとは別の IP アドレスか別のサーバーで待ち受けます。
  • access-control とファイアウォールの両方で送信元を絞り、ip-ratelimit で問い合わせ量を制限し、平文の 53 番は外に出しません。
  • ログは最小限にし、残す内容と期間を決めておきます。
  • 公開前に、許可した端末と外部の端末の両方から動作を確かめます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲