2026年6月9日

2026年6月9日

DoT (DNS over TLS) の鍵を更新する手順|Unbound の証明書・秘密鍵・セッションチケット鍵を止めずに入れ替える

DoT(DNS over TLS)のリゾルバーで「鍵の更新」といえば、主に TLS のサーバー証明書とその秘密鍵の入れ替えを指します。証明書の名前で接続先を確かめているクライアントだけなら、新しい鍵で証明書を取り直してリゾルバーに読み込ませれば済みます。一方、公開鍵のピン(SPKI ピン)で接続先を固定しているクライアントがいる場合は、新しいピンを先に配ってから入れ替えないと、そのクライアントの名前解決が止まります。この記事では、Unbound で DoT を提供している構成を例に、事前の確認、新しい鍵と証明書の用意、反映、確認、切り戻しまでの手順を説明します。

DoQ や DoH の鍵の入れ替えは別の記事で扱っています。考え方は共通ですが、ここでは DoT(TCP 853 番)と Unbound に固有の点を中心に書きます。

DoT で更新の対象になる鍵

  • サーバー証明書の秘密鍵。 TLS の接続でリゾルバーが本物であることを示す鍵です。漏えいの疑いがあるとき、鍵の長さや種類を変えるとき、運用ルールで定期的に変えるときに入れ替えます。
  • サーバー証明書。 秘密鍵に対応する公開鍵と、ホスト名を認証局が署名したものです。期限が来るたびに更新します。鍵を変えずに証明書だけ更新することもできます。
  • TLS セッションチケット鍵。 再接続を速くするためのチケットを暗号化する鍵です。Unbound では tls-session-ticket-keys で指定している場合だけ管理の対象になります。複数台で同じ鍵を共有している場合は、全台をそろえて入れ替えます。

証明書の期限が短くなる方向にあるため(2026年10月現在)、証明書の更新は自動化しておき、秘密鍵を変えるかどうかを意識して選べる状態にしておくのが運用しやすい形です。

事前に確認すること

クライアントが何で接続先を確かめているか

入れ替えの進め方は、ここで決まります。

  • ホスト名で確かめている。 systemd-resolved の DNS=IP#ホスト名、Unbound の転送先の forward-addr: IP@853#ホスト名、Android のプライベート DNS などは、証明書のホスト名と認証局の署名を確かめます。鍵が変わっても、同じホスト名の有効な証明書であれば影響を受けません。
  • 公開鍵のピンで確かめている。 stubby の tls_pubkey_pinset などで SPKI ピンを設定しているクライアントは、鍵が変わるとピンが合わなくなり接続を拒否します。新しい鍵のピンを事前に追加しておく必要があります。
  • 自己署名や社内 CA の証明書を個別に信頼させている。 証明書そのものを配っている場合は、新しい証明書の配布が先です。

利用者に配った設定の手順書や、社内の構成管理を見直して、ピン留めしているクライアントがいないかを洗い出します。分からない場合は、ピン留めしているクライアントがいる前提で進めるほうが安全です。

再読み込みの方法

Unbound は、証明書と鍵を起動時に読み込みます。設定の再読み込み(unbound-control reload)で新しい証明書が使われるかはバージョンや構成によって異なるため、ここでは再起動で反映する前提にします。再起動すると TCP の接続が一度切れ、キャッシュも消えます。利用者の多い時間帯を避け、複数台あるなら1台ずつ入れ替えます。

手順1:現在の証明書と鍵を記録する

切り戻しと確認のために、今の状態を控えます。作業の前に設定ファイルと証明書・鍵のバックアップを取っておきます。

# 今の証明書の期限と発行者
openssl x509 -in /etc/unbound/tls/fullchain.pem -noout -subject -issuer -dates

# 今の公開鍵の SPKI ピン(SHA-256 を Base64 で表したもの)
openssl x509 -in /etc/unbound/tls/fullchain.pem -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary | openssl enc -base64

# バックアップ
sudo cp -a /etc/unbound/tls /etc/unbound/tls.bak-$(date +%Y%m%d)

fullchain.pem の先頭にある証明書(サーバー証明書)の公開鍵が計算の対象です。ピンの値は、クライアントの設定に書かれている値と見比べて、どれが今の鍵かを確かめておきます。

手順2:新しい鍵を作り、ピンを先に配る

ピン留めしたクライアントがいる場合は、証明書を取り直す前に新しい秘密鍵を作り、そのピンをクライアントへ先に追加します。

# 新しい秘密鍵(例は ECDSA P-256)
sudo openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
  -out /etc/unbound/tls/next-privkey.pem
sudo chmod 600 /etc/unbound/tls/next-privkey.pem

# 新しい鍵のピン
sudo openssl pkey -in /etc/unbound/tls/next-privkey.pem -pubout -outform der \
  | openssl dgst -sha256 -binary | openssl enc -base64

stubby の設定では、ピンを複数並べられます。今の鍵と新しい鍵の両方を書いておけば、入れ替えの前後どちらでも接続できます。

# stubby.yml の upstream_recursive_servers の一部(値は例)
  - address_data: 192.0.2.53
    tls_auth_name: "dns.example.com"
    tls_pubkey_pinset:
      - digest: "sha256"
        value: 今の鍵のピン
      - digest: "sha256"
        value: 新しい鍵のピン

クライアントへの配布には時間がかかります。配布が行き渡ったことを確かめてから次に進みます。ピン留めしたクライアントがいない場合、この手順は省けます。

手順3:新しい鍵で証明書を取得する

Let's Encrypt と certbot を使っている場合、証明書の更新時に鍵を作り直すか、今の鍵を使い続けるかを選べます。certbot には --reuse-key(同じ鍵を使い続ける)と --new-key(新しい鍵を作る)のオプションがありますが、手順2で作った鍵を使いたい場合は、その鍵から CSR を作って --csr で渡す方法が分かりやすくなります。

# 新しい鍵から CSR を作る
sudo openssl req -new -key /etc/unbound/tls/next-privkey.pem \
  -subj "/CN=dns.example.com" -addext "subjectAltName=DNS:dns.example.com" \
  -out /root/dns-next.csr

DoT 専用のサーバーで Web サーバーを動かしていない場合は、DNS-01 チャレンジで認証するほうが 80 番を開けずに済みます。オプションの組み合わせは certbot のバージョンや認証方式で変わるため、certbot --help all と公式ドキュメントで確かめてください。なお、--csr で取得した証明書は certbot の自動更新の対象にならないため、次回以降の更新をどう行うか(同じ手順を期限前に繰り返す、など)をあらかじめ決めておきます。社内 CA を使っている場合は、CSR を社内 CA の手順で署名してもらいます。

鍵のピンを使っていない構成で、鍵を定期的に変えるだけなら、--new-key を付けて更新し、手順4へ進む流れでも構いません。

手順4:Unbound に反映する

新しい証明書(中間証明書を含む fullchain)と新しい秘密鍵を、Unbound が読む場所に置きます。ファイルを直接上書きせず、新しいファイルを置いてから名前を入れ替えると、途中で壊れた状態を読まれにくくなります。

cd /etc/unbound/tls
sudo install -m 644 -o root -g unbound /path/to/new-fullchain.pem fullchain.pem.new
sudo install -m 640 -o root -g unbound next-privkey.pem privkey.pem.new

# 鍵と証明書が対応しているか(2つのハッシュが一致すること)
openssl x509 -in fullchain.pem.new -pubkey -noout | openssl sha256
openssl pkey -in privkey.pem.new -pubout | openssl sha256

sudo mv fullchain.pem.new fullchain.pem
sudo mv privkey.pem.new privkey.pem
sudo unbound-checkconf && sudo systemctl restart unbound

所有グループ(例では unbound)は、ディストリビューションの Unbound の実行ユーザーに合わせます。鍵と証明書が対応していないと、Unbound は起動時に失敗します。再起動の前に、上のハッシュの比較で確かめておくと止まる時間を減らせます。

セッションチケット鍵を使っている場合は、新しい鍵ファイルを作って tls-session-ticket-keys の先頭に置き、古い鍵は次回まで2番目に残します。先頭の鍵で新しいチケットを作り、残りの鍵は既存のチケットを読むために使う、という扱いです。詳しくは手元のバージョンの man unbound.conf で確認してください。

certbot の自動更新で証明書だけを更新する場合は、/etc/letsencrypt/renewal-hooks/deploy/ にスクリプトを置き、更新のたびにファイルのコピーと Unbound の再起動を行わせると、更新後も古い証明書を読み込んだまま、という事故を防げます。

手順5:外から確かめる

別の端末から接続し、新しい証明書と鍵が使われていることを確かめます。

# 証明書の期限と、検証結果
openssl s_client -connect dns.example.com:853 -servername dns.example.com \
  -verify_hostname dns.example.com </dev/null 2>/dev/null \
  | grep -E 'Verify return code'
openssl s_client -connect dns.example.com:853 -servername dns.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -dates

# 実際に名前解決できるか
kdig @dns.example.com +tls-ca +tls-hostname=dns.example.com wordpress.org

ピン留めしたクライアントでは、kdig の +tls-pin= オプションに新しいピンを渡して接続できるかを試すと、クライアント側の設定より先に確かめられます。あわせて、Unbound のログに TLS のエラーが増えていないかを見ます。

手順6:古いピンと古い鍵を片付ける

しばらく問題がなければ、クライアントの設定から古い鍵のピンを外します。古いピンを残し続けると、漏えいした古い鍵でも接続を受け入れる状態が続きます。バックアップした古い秘密鍵は、切り戻しの必要がなくなった時点で削除します。漏えいの疑いで入れ替えた場合は、古い証明書の失効を認証局に申請します。

うまくいかないときの切り戻し

  • Unbound が起動しない。 ログで鍵と証明書の不一致か、読み取り権限を確かめます。急ぐ場合はバックアップした tls ディレクトリを戻して再起動します。
  • 一部のクライアントだけつながらない。 ピンの配布漏れが典型例です。古い鍵に戻すとそのクライアントは回復しますが、漏えいが理由の入れ替えなら戻さず、クライアントの設定を直します。
  • 証明書の検証に失敗する。 中間証明書が含まれていないファイルを指定していないか、ホスト名が証明書の名前と一致しているかを確かめます。

まとめ

  • DoT の鍵の更新は、主にサーバー証明書の秘密鍵の入れ替えです。ホスト名で確かめているクライアントは、同じ名前の有効な証明書であれば影響を受けません。
  • SPKI ピンで固定しているクライアントがいる場合は、新しい鍵を先に作り、新旧のピンを配ってから入れ替えます。
  • 反映の前に鍵と証明書の対応を確かめ、Unbound は再起動で読み込ませます。certbot の deploy フックで更新後の再起動を自動化しておくと、古い証明書を使い続ける事故を防げます。
  • 反映後は openssl s_client と kdig で外から確かめ、問題がなければ古いピンと古い鍵を片付けます。

お気軽にご相談ください

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