2026年8月18日

2026年8月18日

mTLS と HSTS の関係:役割の違いと WordPress 管理画面で併用する手順

mTLS(相互TLS認証)と HSTS は、どちらも HTTPS に関わる仕組みですが、守っているものが違います。HSTS は「このサイトには HTTPS でしか接続しない」とブラウザに覚えさせる仕組みで、mTLS は「接続してくる側もクライアント証明書で本人確認する」仕組みです。互いを置き換えるものではなく、HSTS で平文 HTTP への逃げ道をふさぎ、その上で mTLS で接続できる端末を絞る、という重ね方になります。この記事では両者の関係を整理し、WordPress の管理画面を例に、併用するときの順番とつまずきやすい点を説明します。

2つの仕組みの違い

通常の HTTPS では、サーバーだけが証明書を提示し、ブラウザがそれを検証します。HSTS はこの通常の HTTPS を前提に、ブラウザ側の振る舞いを決めるものです。

  • HSTS:サーバーが Strict-Transport-Security ヘッダーを返すと、ブラウザは指定期間(max-age)のあいだ、そのホストへ HTTP で接続しようとしても自動で HTTPS に切り替えます。さらに、サーバー証明書にエラーがあっても利用者が「続行」で回避できなくなります。
  • mTLS:TLS のハンドシェイクでサーバーがクライアント証明書を要求し、信頼する認証局(CA)が発行した証明書を持つ端末だけが接続を完了できます。パスワードの前段に「証明書を持つ端末か」という条件が加わります。

つまり HSTS は「通信路を HTTPS に固定する」、mTLS は「HTTPS の中で相手を認証する」という役割分担です。HSTS 自体はクライアント証明書を扱いませんし、mTLS も HTTP へのダウングレードは防ぎません。

なぜ併用すると意味があるのか

mTLS で管理画面を守っていても、同じホストが HTTP でも応答する構成だと、HTTP 側の設定漏れが抜け道になり得ます。また、HTTPS へのリダイレクトだけに頼っている場合、最初の HTTP リクエストは平文で送られます。HSTS を有効にすると、2回目以降のアクセスはブラウザ内で HTTPS に切り替わるため、こうした隙間が狭まります。

逆に、HSTS だけでは「誰が接続してよいか」は決まりません。管理画面のパスワードが漏れれば、どの端末からでもログインできます。mTLS を加えると、証明書を配布した端末以外はハンドシェイクの段階で弾かれるため、総当たり攻撃や漏えいしたパスワードの悪用に対して1層増やせます。

併用で起きる影響

関係を理解しておかないと困るのは、主に次の場面です。

  • includeSubDomains の範囲:HSTS に includeSubDomains を付けると、サブドメインすべてが HTTPS 必須になります。mTLS 用に admin.example.com のようなホストを分ける場合、そのホストも HTTPS で正しく応答できる状態にしておく必要があります。
  • エラー時に回避できない:HSTS が有効なホストでは、サーバー証明書の期限切れや名前の不一致があると利用者は先へ進めません。mTLS 用に自己署名のサーバー証明書を使うと、この制約に直接ぶつかります。サーバー証明書は公的な CA のもの、クライアント証明書は自前の CA、と分けるのが一般的です。
  • 証明書選択のダイアログ:mTLS を「任意(optional)」にしたホストでは、サーバーがクライアント証明書を求めるため、端末に証明書が入っているとブラウザが選択ダイアログを出すことがあります。公開ページと同じホストで有効にすると、一般の閲覧者に影響が出る可能性があります。
  • 接続の再利用:HTTP/2 では、同じ IP と証明書を共有するホスト間でブラウザが接続を使い回すことがあります。mTLS を要求するホストとしないホストがワイルドカード証明書を共有していると、構成によっては 421(Misdirected Request)などの分かりにくい失敗につながります。mTLS 用のホストは証明書を分けておくと切り分けが楽になります。

手順1:HTTPS と HSTS を短い max-age から始める

まずサイト全体が HTTPS で問題なく動いていることを確認し、HSTS を短い期間で有効にします。Nginx の例です。設定ファイルは変更前にバックアップを取り、ステージングで試してから本番に反映してください。

server {
    listen 443 ssl;
    server_name example.com;

    # まずは5分。問題が無ければ段階的に延ばす
    add_header Strict-Transport-Security "max-age=300" always;
    # …
}

Nginx の add_header は、location ブロックで別の add_header を書くと上位の設定が引き継がれません。location 側でもヘッダーを追加している場合は、そこにも HSTS を書く必要があります。反映後は次のコマンドでヘッダーを確認します。

curl -sI https://example.com/ | grep -i strict-transport-security

問題が無ければ max-age を1日、1週間と延ばし、最終的に1年(31536000秒)にします。preload リストへの登録を考える場合は、max-age が1年以上、includeSubDomains と preload の指定、すべてのサブドメインが HTTPS に対応していることが条件になり、登録の取り消しには時間がかかります。mTLS を試している段階では、preload は見送るのが無難です。

手順2:クライアント証明書を発行する

社内用の CA を作り、管理者ごとにクライアント証明書を発行します。OpenSSL の例です。CA の秘密鍵は証明書発行用の端末に置き、Web サーバーには CA 証明書(ca.crt)だけを置きます。

# 管理画面用のプライベートCA
openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \
  -keyout ca.key -out ca.crt -subj "/CN=Example Admin CA"

# 管理者ごとのクライアント証明書
openssl req -newkey rsa:2048 -nodes \
  -keyout admin1.key -out admin1.csr -subj "/CN=admin1"
openssl x509 -req -in admin1.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -days 365 -out admin1.crt

# ブラウザに取り込むための PKCS#12 ファイル
openssl pkcs12 -export -in admin1.crt -inkey admin1.key -out admin1.p12

OpenSSL 3 系で作った PKCS#12 は、古い OS やブラウザで取り込めないことがあります。その場合は -legacy オプションを試すなど、取り込む環境に合わせて調整してください。p12 ファイルとパスフレーズは別々の経路で渡します。

手順3:管理画面に mTLS をかける

Nginx の ssl_verify_client は server 単位の設定です。同じホストで /wp-admin/ と wp-login.php だけを守る場合は optional にし、location で検証結果を確認します。

server {
    listen 443 ssl;
    server_name example.com;

    ssl_client_certificate /etc/nginx/mtls/ca.crt;
    ssl_verify_client optional;
    ssl_verify_depth 2;

    add_header Strict-Transport-Security "max-age=300" always;

    location = /wp-login.php {
        if ($ssl_client_verify != SUCCESS) { return 403; }
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }

    location ^~ /wp-admin/ {
        if ($ssl_client_verify != SUCCESS) { return 403; }
        try_files $uri $uri/ /index.php?$args;
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass unix:/run/php/php-fpm.sock;
        }
    }
}

注意点として、/wp-admin/admin-ajax.php はフロント側のプラグインが未ログインの閲覧者向けにも使うことがあります。上の設定ではそれも 403 になるため、フロントの Ajax が動いているサイトでは admin-ajax.php を別の location に切り出して例外にするか、影響を確認してから適用します。PHP-FPM のソケットパスは環境に合わせてください。

nginx -t で構文を確認して再読み込みしたら、証明書あり・なしの両方で動作を確かめます。

curl -sI https://example.com/wp-login.php                       # 403 になること
curl -sI --cert admin1.crt --key admin1.key https://example.com/wp-login.php  # 200 になること

手順4:締め出しへの備えと運用

mTLS は設定を誤ると管理者自身が入れなくなります。サーバーに SSH で入れる経路を残し、mTLS の行をコメントアウトして戻せるようにしておきます。また、次の点を運用ルールにしておくと事故が減ります。

  • クライアント証明書の有効期限を一覧で管理し、期限前に再発行する
  • 端末の紛失や退職時に失効させる手段を用意する(ssl_crl で CRL を読ませるなど)
  • CDN を経由している場合、ブラウザの証明書は CDN で終端されオリジンに届かない。CDN 側の機能で mTLS を検証するか、管理画面だけ CDN を通さない経路にする

CDN を使っている構成では、CDN とオリジンの間を mTLS で結ぶ使い方もあります。これは閲覧者ではなく CDN 事業者の証明書だけを受け入れる設定で、オリジンへの直接アクセスを防ぐ目的です。対応状況や設定名は事業者ごとに異なり変わることがあるため(2026年10月現在)、各社のドキュメントで確認してください。

まとめ

  • HSTS は通信路を HTTPS に固定する仕組み、mTLS は HTTPS の中で接続元の端末を認証する仕組みで、役割が違う
  • 併用すると、HTTP への抜け道をふさいだうえで、証明書を配布した端末だけに管理画面を開ける
  • HSTS は短い max-age から始め、includeSubDomains や preload は全ホストの HTTPS 対応を確かめてから
  • Nginx では ssl_verify_client optional と location ごとの検証で管理画面だけを守れる。admin-ajax.php の扱いに注意
  • 締め出し対策として SSH から設定を戻せる経路を残し、証明書の期限と失効を管理する

お気軽にご相談ください

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