2026年6月1日
2026年6月1日
Nginxでクライアント証明書認証(mTLS)を設定する方法:CA作成から管理画面の保護まで
通常の HTTPS では、サーバーが証明書を出し、クライアント(ブラウザなど)がそれを検証して「接続先が本物のサーバーか」を確かめます。mTLS(相互 TLS、クライアント証明書認証)では、これに加えてクライアントも証明書を出し、サーバーが「接続してきたのが許可した端末やシステムか」を確かめます。
API の呼び出し元を限定したい場合や、WordPress の管理画面に決まった端末からしか入れないようにしたい場合に使えます。この記事では、Nginx でクライアント証明書を検証する設定を、証明書の作成から確認まで順に説明します。コマンドと設定は Nginx 1.30・OpenSSL 3.5 で動作を確認しています(2026年10月時点)。
仕組みと必要なファイル
mTLS では、次の2組の証明書を使います。役割を取り違えやすいので、先に整理しておきます。
| ファイル | 役割 | 置き場所 |
|---|---|---|
| サーバー証明書と秘密鍵(例: fullchain.pem / privkey.pem) | サーバーがクライアントに本物であることを示す。通常の HTTPS と同じ | サーバー |
| クライアント CA 証明書(例: client-ca.crt) | クライアント証明書を発行した認証局の証明書。Nginx はこれを使ってクライアント証明書を検証する | サーバー |
| クライアント CA の秘密鍵(例: client-ca.key) | クライアント証明書に署名する鍵 | サーバーとは別の安全な場所 |
| クライアント証明書と秘密鍵(例: client1.crt / client1.key) | クライアントが自分を証明する | 各クライアント |
Nginx の ssl_client_certificate に指定するのは、クライアント証明書ではなくクライアント CA の証明書です。この CA が署名した証明書を持つクライアントだけが接続できます。
サーバー証明書は Let's Encrypt などで取得した通常のもので構いません。クライアント証明書は、社内や取引先など限られた相手に配るものなので、自分で作った CA(プライベート CA)で発行するのが一般的です。公的な CA の証明書を ssl_client_certificate に指定すると、その CA から証明書を買った誰でも通ってしまうため、専用の CA を使います。
手順1: クライアント CA を作る
作業用のディレクトリで、クライアント CA の秘密鍵と証明書を作ります。有効期限は10年(3650日)にしています。
openssl req -x509 -newkey rsa:3072 -sha256 -days 3650 -nodes \
-keyout client-ca.key -out client-ca.crt \
-subj "/CN=Example Client CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
client-ca.key が漏れると、誰でも有効なクライアント証明書を作れてしまいます。サーバーには置かず、アクセスできる人を限った場所に保管します。-nodes は鍵をパスフレーズなしで書き出す指定なので、保管場所で守るか、外してパスフレーズを付けてください。
手順2: クライアント証明書を発行する
クライアントごとに鍵と証明書署名要求(CSR)を作り、CA で署名します。extendedKeyUsage=clientAuth は「クライアント認証用の証明書」であることを示す拡張です。
# クライアントの鍵と CSR
openssl req -new -newkey rsa:2048 -nodes \
-keyout client1.key -out client1.csr \
-subj "/CN=client1/O=Example"
# 証明書に入れる拡張
cat > client.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=clientAuth
EOF
# CA で署名する(有効期限 365日)
openssl x509 -req -in client1.csr -CA client-ca.crt -CAkey client-ca.key \
-CAcreateserial -days 365 -sha256 -extfile client.ext -out client1.crt
できた証明書を確認します。client1.crt: OK と、発行者が Example Client CA になっていれば成功です。
openssl verify -CAfile client-ca.crt client1.crt
openssl x509 -in client1.crt -noout -subject -issuer -enddate -ext extendedKeyUsage
ブラウザに読み込ませる場合は、証明書と鍵を PKCS#12 形式(.p12)にまとめます。書き出すときにパスワードを聞かれるので、取り込む人に別の経路で伝えます。
openssl pkcs12 -export -inkey client1.key -in client1.crt -certfile client-ca.crt -out client1.p12
OpenSSL 3 で作った .p12 を古い OS やソフトが読み込めない場合は、-legacy を付けて古い暗号方式で書き出す方法があります。
手順3: Nginx でクライアント証明書を必須にする
クライアント CA の証明書をサーバーに置きます。秘密鍵(client-ca.key)は置きません。
sudo mkdir -p /etc/nginx/mtls
sudo cp client-ca.crt /etc/nginx/mtls/client-ca.crt
API 用のサイト全体でクライアント証明書を必須にする例です。
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name api.example.com;
# サーバー証明書(通常の HTTPS と同じ)
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# クライアント証明書の検証
ssl_client_certificate /etc/nginx/mtls/client-ca.crt; # クライアント CA の証明書
ssl_verify_client on; # 証明書がない・無効なら拒否
ssl_verify_depth 2;
location / {
proxy_pass http://127.0.0.1:8080;
# 検証結果と利用者の情報をアプリへ渡す
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn;
proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
}
}
主な設定の意味は次のとおりです。
ssl_client_certificate: クライアント証明書を検証するための CA 証明書(PEM 形式)。ここに入れた CA の名前は、接続時に「受け付ける CA の一覧」としてクライアントに送られます。ssl_verify_client:onは証明書を必須にし、検証に失敗した接続を拒否します。optionalは証明書がなくても接続を受け付け、検証結果を変数$ssl_client_verifyに入れます。ssl_verify_depth: 証明書チェーンをたどる深さです。既定値は 1 で、CA が直接署名したクライアント証明書ならこれで足ります。中間 CA を挟む場合は 2 以上にします。$ssl_client_verify: 検証に成功するとSUCCESS、証明書がなければNONE、失敗するとFAILED:理由になります。$ssl_client_s_dnには証明書の主体者(例:O=Example,CN=client1)が入ります。
設定を確認して読み込み直します。
sudo nginx -t && sudo systemctl reload nginx
手順4: curl と openssl で確認する
証明書なしと証明書ありの両方で試します。
# 証明書なし: 400 になる
curl -s -o /dev/null -w '%{http_code}\n' https://api.example.com/
# クライアント証明書あり: アプリの応答が返る
curl -s --cert client1.crt --key client1.key https://api.example.com/
ssl_verify_client on の場合、証明書がない接続や、別の CA が発行した証明書の接続には、Nginx が 400 を返します(エラーページには「No required SSL certificate was sent」または「The SSL certificate error」と表示されます)。エラーログにも検証失敗の理由が記録されるので、うまくいかないときは /var/log/nginx/error.log を確認します。
サーバーがどの CA の証明書を求めているかは、openssl s_client で見られます。
openssl s_client -connect api.example.com:443 -servername api.example.com \
-cert client1.crt -key client1.key </dev/null
出力の Acceptable client certificate CA names の下に CN=Example Client CA が表示されていれば、Nginx はそのクライアント CA で検証しています。
WordPress の管理画面だけに適用する
サイト全体ではなく、/wp-admin/ と wp-login.php だけをクライアント証明書を持つ端末に限る例です。ssl_verify_client optional で証明書の有無にかかわらず接続を受け付け、URL ごとに検証結果を見て拒否します。
map は http ブロック(/etc/nginx/conf.d/ の設定ファイルの、server ブロックの外)に書きます。
# 検証に成功したら 1
map $ssl_client_verify $mtls_ok {
SUCCESS 1;
default 0;
}
# 証明書なしで管理画面とログイン画面に来たら拒否する(admin-ajax.php は除く)
map "$mtls_ok:$uri" $mtls_deny {
default 0;
"~^0:/wp-admin/admin-ajax\.php$" 0;
"~^0:/wp-login\.php$" 1;
"~^0:/wp-admin(/|$)" 1;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_client_certificate /etc/nginx/mtls/client-ca.crt;
ssl_verify_client optional;
ssl_verify_depth 2;
if ($mtls_deny) {
return 403;
}
# 以降は通常の WordPress の設定(root、location、PHP-FPM など)
}
map の正規表現は書いた順に調べられるため、admin-ajax.php の行を /wp-admin の行より前に置いています。admin-ajax.php はサイトの表側(フォームや絞り込みなど)からも呼ばれるので、ここを拒否すると一般の訪問者の操作が動かなくなることがあります。
この設定で、証明書なしの場合は /wp-admin/ と /wp-login.php が 403、トップページと admin-ajax.php は通常どおり表示されます。証明書ありの場合は管理画面にも入れます。
optional にすると、証明書を入れたブラウザでは、サイトの表側を開いたときにも証明書を選ぶ画面が出ることがあります。管理用のホスト名を分けられる場合は、管理用のホストだけ on にする方法もあります。
PHP 側でも検証結果を使いたい場合は、PHP-FPM に渡す location に fastcgi_param SSL_CLIENT_VERIFY $ssl_client_verify; のように追加します。
証明書の失効と更新
社員の退職や端末の紛失で特定のクライアント証明書を無効にする方法は、主に2つあります。
- CRL(失効リスト)を使う: CA で失効させた証明書の一覧(PEM 形式の CRL)を作り、
ssl_crl /etc/nginx/mtls/client-ca.crl;で指定します。CRL には有効期限があり、期限が切れると検証に失敗して全員が接続できなくなるため、定期的に作り直して読み込み直す運用が必要です。 - シリアル番号で拒否する: 台数が少ない場合は、
$ssl_client_serial(証明書のシリアル番号)をmapで照合して 403 を返す方法もあります。シリアル番号はopenssl x509 -in client1.crt -noout -serialで確認できます。
クライアント証明書には有効期限があるため、期限の前に新しい証明書を発行して配り直します。クライアント CA の証明書を作り直した場合は、すべてのクライアント証明書を発行し直す必要があります。
注意点
- TLS をどこで終端しているか: ロードバランサーや CDN(Cloudflare のプロキシなど)で TLS を終端している構成では、クライアントの証明書は Nginx まで届きません。その場合は終端する側で mTLS を設定します。
- 秘密鍵の扱い: クライアント CA の秘密鍵と、配布する .p12 のパスワードは、通常の秘密情報と同じように管理します。
- 段階的に導入する: いきなり
onにすると、証明書をまだ入れていない利用者が全員入れなくなります。最初はoptionalにしてログに$ssl_client_verifyを記録し、全員がSUCCESSになったことを確かめてから拒否に切り替えると安全です。
まとめ
- mTLS では、Nginx の
ssl_client_certificateにクライアント CA の証明書を指定し、ssl_verify_clientでクライアント証明書を検証する - クライアント証明書は専用のプライベート CA で発行し、
extendedKeyUsage=clientAuthを付ける。CA の秘密鍵はサーバーに置かない onはサイト全体で必須、optionalとmapを組み合わせると WordPress の管理画面だけに適用できる- curl の
--cert・--keyとopenssl s_clientで、証明書なしは拒否・ありは通ることを確認する - 失効は CRL かシリアル番号で扱い、証明書の期限と CRL の期限を管理する