2026年6月8日

2026年6月8日

DoH (DNS over HTTPS) の鍵を更新する手順|HTTPS 証明書と秘密鍵を入れ替え、リゾルバーを止めずに反映する

自前で DoH(DNS over HTTPS)のリゾルバーを運用している場合、更新の対象になる「鍵」は、ほとんどが HTTPS の TLS 証明書とその秘密鍵です。DoH は普通の HTTPS の上で DNS をやり取りする仕組みなので、Web サイトの証明書更新とほぼ同じ手順で入れ替えられます。この記事では、TLS をどこで終端しているかの確認から、新しい鍵での証明書の取得、nginx と dnsdist での無停止に近い反映、curl と openssl での確認、切り戻しまでを順に説明します。

DoH で更新の対象になる鍵

DoH(RFC 8484)は、DNS の問い合わせを HTTPS のリクエストとして送る方式です。通常は TCP の 443 番を使い、URL は https://doh.example.com/dns-query のような形になります。鍵まわりは次の3つに分けて考えると整理しやすくなります。

  1. TLS サーバー証明書と秘密鍵。 クライアントが「接続先が本物のリゾルバーか」を確かめるためのもので、運用者が期限や方針に合わせて入れ替えます。この記事の主な対象です。
  2. TLS のセッションチケット鍵。 再接続を速くするための鍵です。多くのサーバーソフトウェアは自動で作り直すため、複数台で鍵ファイルを共有している場合だけ運用者が管理します。
  3. Oblivious DoH(ODoH)の鍵。 送信元 IP とクエリの内容を分離する拡張(RFC 9230)を提供している場合だけ、ターゲット側の公開鍵の配布と更新が必要になります。採用例は限られるため、この記事では扱いません。

DNS の応答の正しさを保証する DNSSEC の鍵(KSK・ZSK)は、DoH とは別の仕組みです。DoH は通信路を暗号化するだけで、ゾーンの署名とは関係しません。

事前に確認すること

TLS をどこで終端しているか

証明書を置き換える場所は、TLS を終端している機器です。DoH の実装そのものが証明書を持っているとは限りません。

  • DoH 実装が直接 443 番で待ち受けている。 dnsdist の DoH 待ち受けなどが該当します。証明書はその実装の設定ファイルで指定しています。
  • nginx などのリバースプロキシが前段にいる。 TLS は nginx で終端し、後段の DoH 実装へは内部で転送しています。置き換えるのは nginx 側の証明書です。
  • ロードバランサーや CDN が前段にいる。 外から見える証明書はそちらの管理画面やサービス側で更新します。オリジン側にも証明書を置いている場合は、両方を把握しておきます。

どれに当たるか分からない場合は、サーバー上で sudo ss -tlnp | grep ':443' を実行し、443 番を待ち受けているプロセスを確認します。

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

ブラウザや OS の DoH 機能は、ホスト名と公的な認証局(CA)でサーバーを検証するのが一般的です。この場合、証明書のホスト名が合っていて信頼された CA が発行していれば、鍵が変わってもクライアント側の作業はありません。

注意が要るのは、公開鍵のハッシュを照合する設定(ピン留め)や、dnscrypt-proxy などで使う DNS スタンプ(sdns://)を配っている場合です。DoH 用のスタンプには証明書チェーン内の証明書のハッシュを含められるため、どの証明書のハッシュを入れたかによっては、鍵の変更や中間証明書の切り替えで接続できなくなります。社内向けに配布しているなら、新しいハッシュを先に配り、新旧両方が有効な期間を作ってから切り替えます。

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

切り戻しに備えて、現在のファイルを控え、期限と公開鍵のハッシュを記録します。作業の前に、サーバー全体のバックアップも取っておきます。

# 外から見えている証明書の発行先・期限・指紋
openssl s_client -connect doh.example.com:443 -servername doh.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -enddate -fingerprint -sha256

# 公開鍵(SPKI)の SHA-256 ハッシュ。ピン留めしている場合はこれと照合する
openssl s_client -connect doh.example.com:443 -servername doh.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -pubkey \
  | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

certbot を使っている場合、証明書は /etc/letsencrypt/live/<名前>/ に、実体は archive/ に世代ごとに残ります。それとは別に、設定ファイルで直接指定しているパスのファイルもコピーしておきます。

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

Let's Encrypt と certbot の組み合わせでは、更新のたびに新しい秘密鍵が作られるのが既定の動作です。--reuse-key を付けて取得した証明書は、明示的にやめるまで同じ鍵を使い続けます。どちらになっているかは /etc/letsencrypt/renewal/<名前>.conf に reuse_key = True があるかで確かめられます。

計画的に鍵を変える場合は、次のように強制的に更新します。発行回数には CA ごとの制限があるため、何度も試すときは --dry-run(ステージング環境)で確認してから本番を実行します。

# 事前の確認(ステージング環境で試すだけで、証明書は置き換わらない)
sudo certbot renew --cert-name doh.example.com --dry-run

# 新しい秘密鍵で取り直す(reuse_key を使っていない場合)
sudo certbot renew --cert-name doh.example.com --force-renewal

鍵の種類を RSA から ECDSA に変えたい場合は --key-type ecdsa を指定します。certbot の既定の鍵の種類や関連オプションはバージョンで変わってきたため、手元の certbot --help all で確認してください(2026年10月現在)。また、証明書の最長有効期間は業界の取り決めで段階的に短くなる方向にあり、更新の回数は今後増えていきます。手作業ではなく、自動更新と次の手順の反映フックで回せる形にしておくと運用が楽になります。

手順3:サーバーに反映する

nginx で TLS を終端している場合

nginx は設定の再読み込み(reload)で新しい証明書を読み込みます。古いワーカーは処理中の接続を終えてから止まるため、DoH の利用者からは切れ目がほとんど見えません。

server {
    listen 443 ssl;
    http2 on;                      # nginx 1.25.1 以降の書き方
    server_name doh.example.com;

    ssl_certificate     /etc/letsencrypt/live/doh.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/doh.example.com/privkey.pem;

    location /dns-query {
        # 後段の DoH 実装への転送設定は、使っている実装の説明に合わせる
        proxy_pass http://127.0.0.1:8053;
    }
}
sudo nginx -t && sudo systemctl reload nginx

ssl_certificate にはサーバー証明書単体ではなく、中間証明書を含む fullchain.pem を指定します。単体を指定すると、一部のクライアントだけが証明書エラーになる状態を作りがちです。

certbot の自動更新に組み込むなら、更新に成功したときだけ実行される deploy フックに書いておきます。

#!/bin/sh
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh(例・実行権限を付ける)
set -e
nginx -t && systemctl reload nginx

dnsdist が直接待ち受けている場合

dnsdist では、ファイルを置き換えたあと、稼働中のままコンソールから証明書を読み直せます。

sudo install -m 0640 -o root -g _dnsdist /etc/letsencrypt/live/doh.example.com/fullchain.pem /etc/dnsdist/tls/fullchain.pem
sudo install -m 0640 -o root -g _dnsdist /etc/letsencrypt/live/doh.example.com/privkey.pem   /etc/dnsdist/tls/privkey.pem
sudo dnsdist -c -e 'reloadAllCertificates()'

_dnsdist は Debian 系パッケージでの実行グループの例です。ps -o user,group -C dnsdist で実際の名前を確かめて置き換えてください。コンソール接続には設定ファイルの controlSocket と setKey が必要です。DoH を HTTP/3 でも提供している場合、そちらまで再読み込みの対象になるかはバージョンで異なる可能性があるため、次の確認で古い証明書が残っていれば systemctl restart dnsdist で切り替えます。

複数台・セッションチケット鍵

複数台で運用している場合は、1台ずつ反映して確認し、問題がなければ残りへ進めます。nginx で ssl_session_ticket_key を指定して鍵ファイルを全台で共有している構成なら、その鍵も定期的に作り直して全台へ同時に配ります。指定していなければ nginx が自動で生成するため、作業は不要です。

手順4:外から確認する

証明書が入れ替わったか、DoH として応答するかの両方を確かめます。サーバー自身ではなく外部のネットワークから実行すると、利用者と同じ経路で確かめられます。

# 新しい証明書の期限と公開鍵ハッシュ(手順1の値と変わっているか)
openssl s_client -connect doh.example.com:443 -servername doh.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -enddate -fingerprint -sha256

# DoH として応答するか(example.com の A を問い合わせる RFC 8484 形式の GET)
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' \
  -H 'accept: application/dns-message' \
  'https://doh.example.com/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'

# curl の DoH 機能を使って、このリゾルバー経由で名前解決する
curl -sS -o /dev/null -w '%{http_code}\n' --doh-url https://doh.example.com/dns-query https://example.com/

2つ目のコマンドで 200 application/dns-message が返れば、DoH の応答として正常です。Knot DNS の kdig が入っている環境なら、kdig @doh.example.com +https example.com A でも確認できます。ブラウザでは、Firefox の DoH 設定にカスタムの URL を入れて実際に閲覧し、名前解決が通るかを見ておくと安心です。

ピン留めやスタンプを配っている場合は、新しいハッシュで接続できることを確かめてから、旧ハッシュを配布先から外します。外すのは、旧鍵に戻す可能性がなくなってからです。

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

  • 一部のクライアントだけ証明書エラーになる。 中間証明書を含まないファイルを指定している可能性が高いです。openssl s_client -showcerts でチェーンが返っているかを見ます。
  • 全クライアントで接続できない。 鍵と証明書の組み合わせの取り違えを疑います。openssl x509 -noout -pubkey -in fullchain.pem と openssl pkey -pubout -in privkey.pem の出力が一致するかで確かめられます。
  • ブラウザでは見えるのに DoH だけ失敗する。 証明書ではなく後段の転送やパスの問題です。/dns-query へのリクエストがアクセスログに残っているか、後段の DoH 実装が起動しているかを確認します。
  • ピン留めクライアントだけ失敗する。 新しいハッシュが届いていない端末です。サーバーを旧鍵に戻すか、端末の設定を更新します。

切り戻すときは、手順1で控えたファイルを元のパスに戻し、nginx の reload か dnsdist の再読み込みを行います。秘密鍵の漏えいが疑われる場合は、古い鍵に戻さず、新しい鍵で取り直したうえで旧証明書を失効させます(certbot なら certbot revoke --cert-path <旧証明書> --reason keycompromise)。

まとめ

  • DoH で運用者が更新する鍵は、主に HTTPS の TLS 証明書と秘密鍵です。DNSSEC の鍵とは別物です。
  • 置き換える場所は TLS を終端している機器(DoH 実装、nginx、ロードバランサーや CDN)です。最初にどこで終端しているかを確かめます。
  • certbot は既定で更新ごとに新しい鍵を作ります。計画的に変えるなら --dry-run で試してから --force-renewal で取り直します。
  • 反映は nginx の reload、dnsdist の reloadAllCertificates() で行い、openssl s_client と curl で証明書と DoH の応答の両方を外から確かめます。
  • ピン留めやスタンプを配っている場合だけ、新旧両方のハッシュを先に配ってから切り替えます。

お気軽にご相談ください

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