2026年6月11日

2026年6月11日

DoQ (DNS over QUIC) の鍵ローテーション手順:ピン留めしたクライアントを切らさずに証明書と秘密鍵を入れ替える

DoQ(DNS over QUIC)で定期的に入れ替える「鍵」は、主に TLS 1.3 のサーバー証明書とその秘密鍵です。ローテーションを安全に進める順番は、新しい鍵を先に作って公開鍵のピン(SPKI ピン)をクライアントへ配る、重複期間を置いてから証明書を差し替える、差し替え後に外から接続して確かめる、の3段階です。この記事では、自前で DoQ リゾルバを運用している人向けに、この順番を具体的なコマンドとともに説明します。

ここで扱うのは計画的な定期ローテーションです。証明書の有効期限だけを延ばして鍵は据え置く「更新」とは別の作業として考えます。

DoQ で入れ替えの対象になるもの

DoQ は RFC 9250 で標準化されたプロトコルで、通常は UDP の 853 番で待ち受け、ALPN には doq を使います。QUIC は TLS 1.3 を組み込んでいるため、サーバー側で管理する秘密の値はいくつかあります。

  • サーバー証明書と秘密鍵:クライアントがサーバーを認証するための鍵。この記事の主な対象
  • セッションチケットの鍵:再接続や 0-RTT を速くするための鍵。多くの実装が内部で自動的に入れ替える
  • QUIC のアドレス検証トークンやステートレスリセット用の値:実装の内部で生成され、再起動で作り直されることが多い

2つ目と3つ目は、実装によって自動で扱われるのが一般的です。設定項目がある場合も、名前や既定値はソフトウェアと版で異なるため(2026年10月現在)、お使いの製品のドキュメントで確認してください。運用者が手順を組む必要があるのは、主に1つ目の証明書と秘密鍵です。

なぜ鍵の入れ替えでクライアントが切れるのか

DoQ のクライアントがサーバーを認証する方法は、大きく2つあります。

1つは、公的な認証局が発行した証明書を、ホスト名(例:dns.example.com)と照合する方法です。この場合、鍵を変えても新しい証明書が正しく発行されていれば、クライアントはそのまま接続できます。

もう1つは、サーバーの公開鍵のハッシュ(SPKI ピン)をクライアントに登録しておく方法です。DNS の暗号化については RFC 7858 や RFC 8310 でこの方式が説明されており、社内の端末や自宅のルーターから自前のリゾルバを使うときに選ばれることがあります。ピン留めしたクライアントは、鍵が変わった瞬間に接続を拒否します。これは攻撃者の証明書を拒むための正しい動きですが、運用者が予告なく鍵を変えた場合も同じように拒否されます。

そのため、ピンを使うクライアントが1台でもあるなら、「新しい鍵のピンを先に配り、古いピンと新しいピンの両方を受け入れる期間を作る」ことがローテーションの中心になります。

ステップ0:現状を棚卸しする

作業の前に、次の点を書き出しておきます。

  • どのクライアントが、ホスト名の検証とピンのどちらで接続しているか
  • 同じ証明書を DoT(TCP 853)や DoH(TCP 443)でも使っているか
  • 証明書の取得方法(Let's Encrypt などを自動更新しているか、手動か)
  • サーバーが複数台あるか(1台ずつ切り替えられるか)

Let's Encrypt の certbot は、既定では更新のたびに新しい秘密鍵を作ります。ピンを使うクライアントがいるのに既定のまま自動更新していると、更新のたびに接続が切れることになります。ピン運用では、更新時に鍵を再利用する設定(--reuse-key)にしておき、鍵の入れ替えはこの記事の手順で計画的に行う形が扱いやすくなります。

ステップ1:次の鍵を作ってピンを計算する

新しい秘密鍵を先に作ります。ここでは ECDSA P-256 の例です。

# 次に使う秘密鍵を作る(サービスのユーザーだけが読めるようにする)
sudo install -d -m 700 /etc/dnsdist/tls
sudo openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out /etc/dnsdist/tls/next.key
sudo chmod 600 /etc/dnsdist/tls/next.key

# 次の鍵の SPKI ピン(SHA-256 / Base64)
sudo openssl pkey -in /etc/dnsdist/tls/next.key -pubout -outform DER \
  | openssl dgst -sha256 -binary | openssl base64

# いま使っている証明書のピン(比較用)
openssl x509 -in /etc/dnsdist/tls/fullchain.pem -noout -pubkey \
  | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | openssl base64

ディレクトリやファイル名は一例です。実際に DoQ を提供しているソフトウェア(dnsdist、AdGuard Home、Unbound など)の設定に合わせて読み替えてください。秘密鍵は Git などのリポジトリに入れず、バックアップも暗号化して保管します。

ステップ2:新旧のピンをクライアントに配り、待つ

ピンを使うクライアントの設定に、いまのピンと次のピンの両方を登録します。クライアントによって、複数のピンを登録できるかどうかは異なります。1つしか登録できないクライアントは、切り替えの時刻に合わせて設定を変える必要があるため、台数と担当者を事前に決めておきます。

すべてのクライアントに設定が行き渡るまで待ちます。端末の設定配布の仕組みがあるなら、その反映状況で確認します。この待ち時間を短く見積もりすぎるのが、ローテーションでいちばん多い失敗です。

ステップ3:次の鍵で証明書を取得する

次の鍵から CSR を作り、その CSR で証明書を発行してもらいます。

sudo openssl req -new -key /etc/dnsdist/tls/next.key \
  -subj "/CN=dns.example.com" -addext "subjectAltName=DNS:dns.example.com" \
  -out /etc/dnsdist/tls/next.csr

# certbot で CSR から発行する例(認証方式は環境に合わせる)
sudo certbot certonly --csr /etc/dnsdist/tls/next.csr --webroot -w /var/www/html \
  --cert-path /etc/dnsdist/tls/next-cert.pem --fullchain-path /etc/dnsdist/tls/next-fullchain.pem \
  --chain-path /etc/dnsdist/tls/next-chain.pem

CSR から発行した証明書は certbot の自動更新の対象になりません。次の更新も同じ手順で行うか、ローテーションが終わった後で certbot の管理下に戻すかを決めておきます。なお、公的な証明書の有効期間は今後さらに短くなる方針が業界で示されているため(2026年10月現在)、更新作業は自動化を前提に考えておくと後が楽になります。

ステップ4:証明書を差し替えて読み込み直す

発行された証明書の公開鍵が次の鍵と一致することを確かめてから差し替えます。

# 新しい証明書のピンがステップ1の値と一致するか
openssl x509 -in /etc/dnsdist/tls/next-fullchain.pem -noout -pubkey \
  | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | openssl base64

# 有効期間とホスト名を確認
openssl x509 -in /etc/dnsdist/tls/next-fullchain.pem -noout -dates -subject -ext subjectAltName

差し替えの前に、いまの証明書と鍵を別名でコピーしておきます。問題が起きたときにすぐ戻せるようにするためです。そのうえで、設定ファイルが参照するパスに新しいファイルを置き、サービスに読み込ませます。

sudo cp -p /etc/dnsdist/tls/fullchain.pem /etc/dnsdist/tls/fullchain.pem.prev
sudo cp -p /etc/dnsdist/tls/privkey.pem /etc/dnsdist/tls/privkey.pem.prev
sudo cp /etc/dnsdist/tls/next-fullchain.pem /etc/dnsdist/tls/fullchain.pem
sudo cp /etc/dnsdist/tls/next.key /etc/dnsdist/tls/privkey.pem

# dnsdist の例:コンソールから証明書を読み込み直す
sudo dnsdist -c -e 'reloadAllCertificates()'
# 読み込み直しが DoQ の待ち受けに効かない版では再起動する
sudo systemctl restart dnsdist

dnsdist の reloadAllCertificates() がどの待ち受け(DoT、DoH、DoQ)に効くかは版によって違いがあるため、ステージングで確認してから本番で使ってください。AdGuard Home や Unbound の場合も、証明書の読み込み直しで済むのか再起動が必要なのかを各ドキュメントで確認します。再起動すると、つながっている QUIC の接続はいったん切れ、クライアントは再接続します。サーバーが複数台あるなら、1台ずつ切り替えて様子を見るのが安全です。

ステップ5:外から接続して確かめる

サーバーの外にある端末から、DoQ で実際に問い合わせます。Knot DNS に付属する kdig は、比較的新しい版で +quic に対応しています。

# ホスト名の検証つきで DoQ 問い合わせ
kdig @dns.example.com +quic +tls-hostname=dns.example.com example.com A

# 新しいピンで接続できるか
kdig @dns.example.com +quic +tls-pin=NEW_PIN_BASE64 example.com A

# AdGuard の dnslookup を使う場合
dnslookup example.com quic://dns.example.com:853

NEW_PIN_BASE64 にはステップ1で計算した値を入れます。古いピンだけを指定した問い合わせが失敗し、新しいピンで成功すれば、入れ替えは意図どおりです。同じ証明書を DoT でも使っている場合は、openssl s_client -connect dns.example.com:853 -servername dns.example.com で TCP 側の証明書も新しくなったかを見ておきます。

ステップ6:古いピンと古い鍵を片付ける

すべてのクライアントが新しい鍵で問題なく接続していることをログで確かめたら、クライアント側の設定から古いピンを外します。このとき、次の次の鍵をすでに作ってピンを登録しておくと、次回のローテーションや緊急時に重複期間をすぐ作れます。

古い秘密鍵は、戻す必要がなくなった時点でサーバーから消し、バックアップの保管期間もあわせて見直します。

鍵が漏れた疑いがあるとき

秘密鍵の漏えいが疑われる場合は、計画的なローテーションとは違い、重複期間を待てません。証明書の失効を認証局に申請し、新しい鍵へすぐに切り替えます。事前に「予備の鍵」のピンをクライアントに登録してあれば、その鍵に切り替えるだけで接続を保てます。予備のピンがない場合は、ピン留めしたクライアントが一時的につながらなくなる前提で、関係者へ連絡してから作業します。

まとめ

  • DoQ で運用者が入れ替えるのは主に TLS 証明書と秘密鍵で、セッション関連の鍵は多くの実装が自動で扱う
  • ホスト名の検証だけのクライアントは鍵を変えても切れにくいが、SPKI ピンを使うクライアントは即座に切れる
  • 次の鍵を先に作り、新旧のピンを配って待ってから証明書を差し替える
  • 差し替え後は外から kdig +quic などでホスト名とピンの両方を確かめる
  • 予備の鍵のピンを常に登録しておくと、次回と緊急時の作業が楽になる

お気軽にご相談ください

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