2026年6月10日
2026年6月10日
DoQ (DNS over QUIC) の鍵ローテーション手順|証明書と秘密鍵の入れ替え、ピン留めクライアントへの事前告知、反映の確認
DoQ(DNS over QUIC)のサーバーで運用者が入れ替える「鍵」は、主に TLS 証明書とその秘密鍵です。QUIC の通信を暗号化する鍵は接続ごとに自動で更新されるため、手作業の対象ではありません。この記事では、DoQ で扱う鍵の種類を整理したうえで、dnsdist を例に、新しい鍵と証明書の用意、公開鍵をピン留めしているクライアントへの事前告知、無停止に近い入れ替え、反映の確認と切り戻しまでの手順を説明します。
DoQ で「鍵」と呼ばれるもの
DoQ(RFC 9250)は QUIC の上で DNS をやり取りする方式で、通常は UDP の 853 番を使います。QUIC は TLS 1.3 を組み込んでいるため、鍵まわりは TLS とほぼ同じ考え方になります。ローテーションの話をするときは、次の3つを分けて考えると混乱しません。
- サーバー証明書と秘密鍵。 クライアントが「接続先が本物のリゾルバーか」を確かめるためのものです。運用者が期限や方針に合わせて入れ替えます。この記事の主な対象です。
- セッション再開用の鍵(セッションチケットの鍵)。 再接続を速くするための鍵で、多くの実装が内部で自動的に作り直します。複数台で同じ鍵ファイルを共有している場合だけ、運用者が配る必要があります。
- パケット保護の鍵。 接続ごとに TLS のハンドシェイクで作られ、QUIC の鍵更新(Key Update)の仕組みで接続中にも切り替わります。設定で触るものではありません。
「DoQ の鍵を定期的に回したい」という要件は、たいてい 1 を指しています。2 と 3 は使っているソフトウェアの既定動作を確認しておけば足ります。
事前に確認すること
クライアントが何で接続先を確かめているか
入れ替えの難しさは、クライアントの検証方法で決まります。
- ホスト名と公的な CA で検証している場合。 証明書のホスト名が合っていて、信頼された CA が発行していれば、鍵が変わっても影響はありません。一般的な構成はこちらです。
- 公開鍵(SPKI)のハッシュをピン留めしている場合。 DoT/DoQ のクライアントの中には、サーバー公開鍵のハッシュを設定して照合するものがあります。鍵を変えると即座に接続できなくなるため、新しいピンを事前に配る必要があります。
- **DNS スタンプ(
sdns://)で配布している場合。** スタンプには証明書チェーン内の証明書のハッシュを含められます。どの証明書(サーバー証明書か中間 CA か)のハッシュを入れたかによって、鍵の変更や CA 側の中間証明書の切り替えで影響を受けます。
社内向けにピンやスタンプを配っているなら、配布先の一覧と更新方法をこの段階で洗い出しておきます。
使っている実装と再読み込みの方法
DoQ に対応したサーバーソフトウェアは、dnsdist、AdGuard Home、Technitium DNS Server、Knot DNS(権威側)などがあります。対応状況や設定方法はバージョンで変わるため、手元のドキュメントで確認してください(2026年10月現在)。以下は dnsdist で DoT(TCP 853)と DoQ(UDP 853)を同じ証明書で待ち受けている例です。
-- /etc/dnsdist/dnsdist.conf(例)
newServer({address="127.0.0.1:5300"}) -- 後段のリゾルバー
addTLSLocal("0.0.0.0:853", "/etc/dnsdist/tls/fullchain.pem", "/etc/dnsdist/tls/privkey.pem")
addDOQLocal("0.0.0.0:853", "/etc/dnsdist/tls/fullchain.pem", "/etc/dnsdist/tls/privkey.pem")
-- 稼働中に操作するためのコンソール(ローカルからのみ)
controlSocket("127.0.0.1:5199")
setKey("your-console-key-base64")
setKey に渡す値は dnsdist のコンソールで makeKey() を実行して作ります。記事中の値はダミーです。
入れ替えの手順
1. 現在の鍵と証明書を記録する
切り戻しに備えて、現在のファイルをコピーし、公開鍵のハッシュを控えます。
sudo cp -a /etc/dnsdist/tls /etc/dnsdist/tls.bak-$(date +%Y%m%d)
# 現在の証明書の期限と公開鍵ハッシュ(SPKI の SHA-256、Base64)
openssl x509 -in /etc/dnsdist/tls/fullchain.pem -noout -subject -enddate
openssl x509 -in /etc/dnsdist/tls/fullchain.pem -noout -pubkey \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary | openssl enc -base64
2. 新しい秘密鍵を作り、ピンを先に配る
ピン留めしているクライアントがある場合は、証明書を発行する前に新しい鍵を作り、その公開鍵ハッシュを「旧ピンと新ピンの両方」としてクライアントへ配ります。こうすると、サーバー側を切り替えた瞬間にどちらの鍵でも接続できます。
# ECDSA P-256 の新しい秘密鍵
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out next-privkey.pem
chmod 600 next-privkey.pem
# 新しい鍵の公開鍵ハッシュ(クライアントへ配るピン)
openssl pkey -in next-privkey.pem -pubout -outform der \
| openssl dgst -sha256 -binary | openssl enc -base64
配布してからサーバーを切り替えるまでの猶予は、クライアントが設定を受け取るまでの期間で決めます。手動で設定している端末があるなら、数日から数週間見ておくと安全です。
ピン留めをしていない場合は、この手順は不要です。
3. 新しい証明書を用意する
Let's Encrypt を certbot で使っている場合、更新のたびに新しい秘密鍵が作られる設定と、--reuse-key で同じ鍵を使い続ける設定があります。ピン留めがなければ前者で自動更新に任せるのが楽です。ピン留めがある場合は普段は --reuse-key で鍵を固定し、計画したタイミングでだけ鍵を変えると、ピンの配布と鍵の変更を合わせやすくなります。certbot の既定値や鍵の種類に関するオプションはバージョンで変わっているため、certbot --help all で確認してください。
自分で作った鍵を使う場合は、その鍵で CSR を作り、CA に提出します。
openssl req -new -key next-privkey.pem -subj "/CN=dns.example.com" \
-addext "subjectAltName=DNS:dns.example.com" -out next.csr
4. サーバーへ反映する
ファイルを置き換え、dnsdist に再読み込みさせます。certbot で自動化する場合は、更新成功時に実行される deploy フックに書いておきます。
#!/bin/sh
# /etc/letsencrypt/renewal-hooks/deploy/dnsdist.sh(例)
set -e
case " $RENEWED_DOMAINS " in
*" dns.example.com "*) ;;
*) exit 0 ;;
esac
install -m 0640 -o root -g _dnsdist "$RENEWED_LINEAGE/fullchain.pem" /etc/dnsdist/tls/fullchain.pem
install -m 0640 -o root -g _dnsdist "$RENEWED_LINEAGE/privkey.pem" /etc/dnsdist/tls/privkey.pem
dnsdist -c -e 'reloadAllCertificates()'
_dnsdistは Debian 系パッケージでの実行グループの例です。ps -o user,group -C dnsdistで実際の名前を確かめて置き換えます。reloadAllCertificates()は稼働中のまま証明書を読み直すコンソールコマンドです。DoT や DoH の待ち受けが対象ですが、DoQ や DoH3 の待ち受けまで含まれるかはバージョンにより異なる可能性があります。次の確認で DoQ 側が古い証明書のままなら、systemctl restart dnsdistで切り替えます。- 再起動すると QUIC の接続はいったん切れ、クライアントは新しく接続し直します。DNS は短い問い合わせなので影響は小さいものの、利用の少ない時間帯に行うと安心です。
複数台で運用している場合は、1台ずつ反映して確認し、問題がなければ残りへ進めます。セッションチケットの鍵をファイルで共有している構成なら、全台で同じ鍵に揃うよう同時に配ります。
反映を確認する
DoQ 側と DoT 側の両方で、新しい証明書が返っているかを確認します。
# DoQ: 名前解決できるか、証明書をホスト名で検証できるか(Knot DNS 3.3 以降の kdig)
kdig @dns.example.com +quic +tls-ca +tls-hostname=dns.example.com example.com A
# 新しいピンで接続できるか(ピン留めクライアントの動作を再現)
kdig @dns.example.com +quic +tls-pin=NEW_PIN_BASE64 example.com A
# DoT 側(TCP 853)の証明書の期限
openssl s_client -connect dns.example.com:853 -servername dns.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -enddate
kdig に -d を付けると、証明書チェーンの情報が表示されるバージョンもあります。OpenSSL 3.2 以降では s_client に QUIC 用のオプションが追加されているので、使える環境なら -quic -alpn doq で DoQ 側の証明書を直接見ることもできます。
確認できたら、ピン留めクライアントの設定から旧ピンを外します。外すのは、旧鍵に戻す可能性がなくなってからにします。
うまくいかないときの切り戻し
- DoQ だけ接続できなくなった。 UDP 853 番がファイアウォールで閉じられていないか、DoQ 側だけ古い設定を読んでいないかを確認します。DoT(TCP)は通るのに DoQ(UDP)だけ失敗する場合は、ほとんどがネットワーク側の UDP の扱いか、DoQ 側の再読み込み漏れです。
- ピン留めクライアントだけ失敗する。 新ピンが配布されていない端末です。サーバーを旧鍵に戻すか、端末の設定を更新します。
- 全クライアントで証明書エラー。 fullchain ではなくサーバー証明書単体を指定している、鍵と証明書の組み合わせが違う、といったファイルの取り違えを疑います。
openssl x509 -noout -pubkeyとopenssl pkey -puboutの出力を比べると、組み合わせが正しいか確かめられます。
切り戻すときは、手順1で取ったバックアップを戻して再読み込み(または再起動)します。
まとめ
- DoQ で運用者が回す鍵は、主に TLS のサーバー証明書と秘密鍵です。QUIC のパケット保護の鍵は自動で更新されます。
- 公開鍵をピン留めしているクライアントや DNS スタンプがある場合は、新しい鍵を先に作り、新旧両方のピンを配ってから切り替えます。
- dnsdist では証明書を置き換えて
reloadAllCertificates()を実行し、DoQ 側に反映されていなければ再起動します。 - 反映後は
kdig +quicとopenssl s_clientで DoQ・DoT の両方を確かめ、旧ピンは戻す可能性がなくなってから外します。