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 番は外に出しません。- ログは最小限にし、残す内容と期間を決めておきます。
- 公開前に、許可した端末と外部の端末の両方から動作を確かめます。