2026年9月3日
2026年9月3日
Nginx ロードバランサーでSSL終端する手順とWordPress側の設定
Nginx をロードバランサー(LB)として前段に置き、HTTPS の暗号化を LB で終わらせて(SSL終端)、背後の Web サーバーへは内部ネットワークで HTTP 転送する構成の作り方を説明します。LB 側の Nginx 設定、バックエンドへ正しい情報を渡すヘッダー、WordPress がリダイレクトループを起こさないための設定、LB とバックエンドの間も暗号化する場合の設定、動作確認の方法を順に扱います。設定はオープンソース版の Nginx を前提にしています(2026年10月時点)。
SSL終端の構成と、先に決めておくこと
SSL終端とは、クライアントとの TLS 通信を LB で復号し、LB からバックエンドまでは平文(または別の TLS)で通信する方式です。証明書の管理を LB に集約でき、バックエンドは TLS の処理をしなくて済みます。
作業前に次の3点を決めておくと、後の手戻りが減ります。
- LB とバックエンドの間を平文にしてよいか:同一 VPC やプライベートネットワーク内に閉じていて、第三者が経路に入れないことが前提です。経路が信頼できない場合や、規程で全区間の暗号化を求められる場合は、後述の「再暗号化」を選びます。
- バックエンドへ外部から直接アクセスできないようにするか:バックエンドが公開 IP で 80 番を受け付けていると、LB を迂回したアクセスや、偽の転送ヘッダーを付けたリクエストを受けてしまいます。ファイアウォールやセキュリティグループで「LB からのみ許可」にします。
- 証明書をどこで発行・更新するか:LB が1台なら LB 上で certbot を動かすのが簡単です。LB を複数台にする場合は、証明書の配布方法(共有ストレージ、構成管理ツール、DNS 認証での個別発行など)を決めておきます。
以下の例では、LB が 203.0.113.10(公開)、バックエンドが 10.0.0.11 と 10.0.0.12(プライベート、80番)、ドメインが example.com という想定です。値は自分の環境に読み替えてください。作業はステージング環境で試し、本番の設定ファイルは事前にバックアップを取ってから進めます。
手順1:LB 側の Nginx に upstream と HTTPS の server を書く
LB の設定ファイル(例:/etc/nginx/conf.d/lb.conf)に、バックエンドの一覧と、443番で待ち受ける server ブロックを書きます。
upstream wp_backend {
server 10.0.0.11:80 max_fails=3 fail_timeout=30s;
server 10.0.0.12:80 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name example.com;
# certbot の webroot 認証を使う場合はチャレンジ用パスを残す
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
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_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
client_max_body_size 64m;
location / {
proxy_pass http://wp_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
設定の要点は次のとおりです。
ssl_certificateには、サーバー証明書だけでなく中間証明書を含むfullchain.pemを指定します。cert.pemだけを指定すると、ブラウザでは表示できても、一部のクライアントや API 連携で証明書チェーンのエラーになることがあります。http2 on;は Nginx 1.25.1 以降の書き方です。それより古い Nginx ではlisten 443 ssl http2;と書きます。バージョンはnginx -vで確認します。ディストリビューション標準パッケージの Nginx のバージョンは OS によって異なります。keepalive、proxy_http_version 1.1、proxy_set_header Connection ""の3つはセットです。これでバックエンドへの接続を使い回せるようになり、リクエストごとに TCP 接続を張り直す手間が減ります。どれか1つが欠けると接続は再利用されません。client_max_body_sizeは LB 側にも必要です。バックエンドで大きなファイルのアップロードを許可していても、LB の既定値(1MB)で止まり「413 Request Entity Too Large」になります。max_failsとfail_timeoutは、転送に失敗したバックエンドを一定時間外す「受動的なヘルスチェック」です。定期的に URL を叩いて死活監視する能動的なヘルスチェック(health_checkディレクティブ)は商用版の NGINX Plus の機能で、オープンソース版にはありません。
手順2:設定を検証して反映する
設定を書いたら、文法チェックをしてから再読み込みします。
sudo nginx -t
sudo systemctl reload nginx
設定に誤りがある状態で restart すると Nginx が起動せず、サイト全体が止まります。reload は新しい設定に問題があれば古い設定のまま動き続けるので、本番では nginx -t の後に reload するのを基本にします。
手順3:バックエンド側で実IPとプロトコルを受け取る
SSL終端すると、バックエンドから見た接続元はすべて LB の IP になり、プロトコルも HTTP になります。このままではアクセスログに LB の IP しか残らず、WordPress は「HTTP で来たリクエスト」だと判断します。
実IPの復元(バックエンドの Nginx)
バックエンドも Nginx なら、realip モジュール(ngx_http_realip_module)で LB を信頼する範囲を指定します。server ブロックか http ブロックに書きます。
set_real_ip_from 10.0.0.0/24; # LB のプライベート IP 範囲
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from には LB の IP(範囲)だけを書きます。ここを 0.0.0.0/0 のように広げると、誰でも X-Forwarded-For を偽装して接続元 IP を詐称できてしまい、IP 制限、ログ解析、ログイン試行の制限が意味をなさなくなります。バックエンドが Apache の場合は mod_remoteip の RemoteIPHeader と RemoteIPInternalProxy で同じことを行います。
WordPress に HTTPS だと伝える(wp-config.php)
WordPress は is_ssl() で HTTPS かどうかを判定しますが、SSL終端の後ろでは HTTP と判定されます。サイトアドレスが https:// なのにリクエストは HTTP と判定されるため、管理画面へのアクセスでリダイレクトが繰り返される(ERR_TOO_MANY_REDIRECTS)、ページ内の CSS や画像が http:// で出力されて混在コンテンツになる、といった症状が出ます。
これを防ぐには、wp-config.php の require_once ABSPATH . 'wp-settings.php'; より前に次を追記します。
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
&& 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
このコードは X-Forwarded-Proto ヘッダーを信用します。前提として、バックエンドへは LB 経由でしか到達できない状態にしておきます。外部から直接バックエンドに届く状態だと、このヘッダーは誰でも付けられます。
手順4:経路も暗号化したい場合(再暗号化)
LB とバックエンドの間も暗号化したい場合は、LB で一度復号したうえで、バックエンドへ HTTPS で転送し直します。バックエンドは 443 番で TLS を受け付け、内部 CA などで発行した example.com 名義の証明書を持たせておきます。LB 側では、HTTPS 用の upstream を別に定義し、手順1の location / を次のように置き換えます。
upstream wp_backend_tls {
server 10.0.0.11:443 max_fails=3 fail_timeout=30s;
server 10.0.0.12:443 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
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_protocols TLSv1.2 TLSv1.3;
client_max_body_size 64m;
location / {
proxy_pass https://wp_backend_tls;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_ssl_server_name on;
proxy_ssl_name example.com;
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
proxy_ssl_trusted_certificate /etc/nginx/backend-ca.pem;
proxy_ssl_session_reuse on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
proxy_ssl_verify の既定値は off で、off のままだとバックエンドの証明書を検証しません。暗号化はされても通信相手が本物かは確認していない状態になるため、内部 CA の証明書を proxy_ssl_trusted_certificate に指定して検証を有効にします。proxy_ssl_name はバックエンドの証明書と照合する名前で、proxy_ssl_server_name on で同じ名前を SNI として送ります。内部 CA に中間 CA がある場合は、proxy_ssl_verify_depth(既定値 1)を階層に合わせて増やします。
この構成ではバックエンドから見ても HTTPS なので、手順3の wp-config.php の追記は無くても動きますが、残しておいても害はありません。CPU 負荷と設定の手間は増えるので、要件と相談して選びます。
手順5:動作確認
設定後は、外側(クライアント)と内側(バックエンド)の両方から確認します。
# 証明書の名義・発行者・有効期限
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# 証明書チェーンの検証結果(0 (ok) なら中間証明書も揃っている)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| grep "Verify return code"
# HTTP から HTTPS へのリダイレクトと応答ヘッダー
curl -sI http://example.com/
curl -sI https://example.com/wp-login.php
続けて、次の点を確認します。
- バックエンドのアクセスログに、LB の IP ではなく実際の接続元 IP が記録されているか。
- 管理画面にログインでき、リダイレクトループが起きないか。
- ページのソースで、CSS・JS・画像の URL が
https://になっているか。 - バックエンドを1台止めたときに、もう1台へ振り分けが続くか(LB のエラーログに upstream の失敗が記録されます)。
最後に証明書の更新です。certbot を使っている場合は sudo certbot renew --dry-run で更新処理が通るかを試します。更新後に Nginx を reload しないと新しい証明書が読み込まれないため、--deploy-hook "systemctl reload nginx" を付けて更新するか、/etc/letsencrypt/renewal-hooks/deploy/ に reload するスクリプトを置いておきます。
まとめ
- SSL終端は証明書管理を LB に集約できる一方、LB とバックエンドの間の経路が信頼できることが前提です。信頼できない場合は再暗号化を選びます。
- LB 側では
fullchain.pemの指定、X-Forwarded-*ヘッダーの付与、keepalive の3点セット、client_max_body_sizeを設定します。 - バックエンドは LB からのみ到達できるように制限し、realip で信頼する範囲を LB の IP に限定します。
- WordPress では
wp-config.phpでX-Forwarded-Protoを見て$_SERVER['HTTPS']を設定し、リダイレクトループと混在コンテンツを防ぎます。 - 再暗号化では HTTPS 用の upstream を定義し、
proxy_ssl_verify onでバックエンドの証明書を検証します。
関連記事: