2026年8月10日
2026年8月10日
NginxでHTTPからHTTPSへリダイレクトする設定方法
Nginx で HTTP から HTTPS へ転送するには、ポート80で待ち受ける server ブロックを用意し、return 301 https://ドメイン$request_uri; の1行で返すのが基本です。この記事では、その設定の書き方と理由、Let's Encrypt の更新を止めないための例外、ロードバランサや CDN の背後にある場合のリダイレクトループの防ぎ方、WordPress 側で URL を HTTPS に切り替える手順、反映後の確認方法までを説明します。
なお、www あり・なしの統一もあわせて行う場合の4通りの組み合わせの整理は、別記事「Nginxでwwwあり・なしを統一するリダイレクト設定」で扱っています。この記事では「HTTP の入口をどう HTTPS に流すか」に絞ります。
前提: HTTPS 側が先に動いていること
リダイレクトを入れる前に、https://example.com/ が証明書エラーなしで表示される状態にしておきます。証明書が無いまま HTTP を HTTPS に転送すると、訪問者はブラウザの警告画面しか見られなくなります。Let's Encrypt を Certbot で取得する場合、Nginx プラグインを使うと証明書の設定とあわせてリダイレクトの追加も提案されます。Certbot の対話の内容やオプションは版によって変わるため、実行時の表示を確認してください(2026年10月現在)。
作業前には /etc/nginx 一式のバックアップを取り、WordPress のデータベースもバックアップしておきます。
sudo cp -a /etc/nginx /etc/nginx.bak-$(date +%Y%m%d)
基本の設定
# HTTP: すべて HTTPS へ転送する
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Let's Encrypt の HTTP-01 認証用(webroot 方式で更新する場合)
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type "text/plain";
}
location / {
return 301 https://example.com$request_uri;
}
}
# HTTPS: 本来のサイト
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.php index.html;
# 以下、WordPress の設定
}
return を使う理由
古い記事では rewrite ^(.*)$ https://$host$1 permanent; や、1つの server ブロックの中で if ($scheme = http) と分岐する書き方を見かけます。どちらも動きますが、return は正規表現の評価をせずにその場で応答を返すため、処理が単純で読み違いも起きにくくなります。Nginx の公式ドキュメントでも、if の多用は避けるよう案内されています。HTTP 用と HTTPS 用で server ブロックを分けておけば、分岐そのものが不要です。
$request_uri でパスとクエリを引き継ぐ
$request_uri には、パスとクエリ文字列(? 以降)が元の形のまま入っています。これを付けないと、http://example.com/blog/post-1/?ref=mail へのアクセスがすべてトップページに飛ばされ、外部からのリンクやブックマークが機能しなくなります。
転送先のホスト名は固定で書く
https://$host$request_uri と書く例も多くありますが、$host はリクエストの Host ヘッダーから決まる値です。この server ブロックが既定のサーバー(default_server)を兼ねていると、想定外のホスト名でも転送してしまいます。転送先は正規のドメインを直接書くほうが安全で、www の統一も同時に行えるため転送が1回で済みます。
301 と 308 の違い
301 は恒久的な移転を示し、検索エンジンにも新しい URL を正規として伝えます。ただし仕様上、ブラウザは 301 を受けると POST を GET に変えて再送することがあります。通常のページ閲覧なら 301 で問題ありませんが、外部サービスが HTTP の URL へ POST してくる(Webhook など)場合は、メソッドを保つ 308 を使うか、送信元の設定を HTTPS に直します。Google 検索は 308 も 301 と同じく恒久的なリダイレクトとして扱うと案内しています。
Let’s Encrypt の更新を止めない
Certbot を webroot 方式で使っている場合、更新時には http://example.com/.well-known/acme-challenge/... へのアクセスが発生します。Let's Encrypt の検証は HTTPS へのリダイレクトをたどれるため、多くの場合はリダイレクトがあっても更新できます。それでも、上の例のように認証用パスだけは HTTP のまま応答させておくと、HTTPS 側の設定ミスや証明書切れのときにも更新が止まりにくくなります。root に指定したディレクトリは Certbot の --webroot-path と同じにしてください。
更新できるかどうかは、本番の証明書に影響しない次のコマンドで試せます。
sudo certbot renew --dry-run
ロードバランサや CDN の背後にある場合
Cloudflare やクラウドのロードバランサで HTTPS を終端し、Nginx へは HTTP で転送している構成では、Nginx から見るとすべてのアクセスが HTTP です。ここに上の転送を入れると「HTTPS で来たのに HTTPS へ転送」を繰り返し、ブラウザに「リダイレクトが繰り返し行われました」と表示されます。
この場合は次のどちらかにします。
- HTTP から HTTPS への転送はロードバランサや CDN 側の機能で行い、Nginx では転送しない
- プロキシが付ける
X-Forwarded-Protoを見て、元が HTTP のときだけ転送する
server {
listen 80;
server_name example.com;
# 信頼できるプロキシからの接続だけを受け付けていることが前提
if ($http_x_forwarded_proto = "http") {
return 301 https://example.com$request_uri;
}
# 以下、WordPress の設定
}
if の中に return だけを書く形は、Nginx の公式ドキュメントでも問題なく動く使い方として挙げられています。X-Forwarded-Proto はクライアントが自由に偽装できるヘッダーなので、Nginx への直接アクセスをファイアウォールで塞ぎ、プロキシ経由の通信だけを受け付けていることが前提です。Cloudflare を使う場合は、SSL/TLS の暗号化モードが「Flexible」だとループの原因になりやすいため、オリジンにも証明書を入れて「Full (strict)」にする構成が推奨されています(画面の表記は変わることがあります)。
WordPress 側にも「元の接続は HTTPS だった」と伝える必要があります。wp-config.php の /* That's all, stop editing! */ より上に次を追記します。
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
これが無いと、WordPress が is_ssl() を偽と判定し、管理画面へのログイン時などに独自のリダイレクトを返してループすることがあります。
WordPress 側の URL を HTTPS に切り替える
Nginx の転送だけでは、WordPress が出力するリンクや画像の URL は http:// のままです。転送で表示はできますが、毎回リダイレクトが挟まり、画像やスクリプトが混在コンテンツとしてブロックされることもあります。
- 管理画面の「設定」→「一般」で、WordPress アドレス(URL)とサイトアドレス(URL)を
https://に変更する - 投稿本文などに残っている
http://の URL を置換する
置換は WP-CLI を使うと、シリアライズされたデータも壊さずに処理できます。まず --dry-run で件数だけを確認し、データベースのバックアップを取ってから実行します。
wp db export before-https.sql
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid --dry-run
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid
guid 列は投稿の識別子として使われるため、置換の対象から外すのが一般的です。
反映と確認
sudo nginx -t && sudo systemctl reload nginx
curl -sI "http://example.com/sample-page/?utm_source=test" | grep -iE '^(HTTP|Location)'
curl -sIL "http://www.example.com/" | grep -iE '^(HTTP|Location)'
1つ目では 301 と、パスとクエリを保った Location: https://example.com/sample-page/?utm_source=test が返ることを確認します。2つ目の -L は転送を最後までたどるオプションで、200 に到達するまでの転送回数が分かります。http://www から https://www を経て https:// へ、のように2回以上転送されている場合は、転送先のホスト名を固定で書けているか見直します。ブラウザで確認するときは、キャッシュされた301が残っていることがあるため、プライベートウィンドウを使うと確実性が上がります。
HTTPS で安定して動くことを確かめてから、HSTS の導入を検討します。
まとめ
- ポート80の
serverブロックでreturn 301 https://正規ドメイン$request_uri;を返すのが基本 ifやrewriteよりreturnが単純で読み違いが起きにくい。転送先ホストは固定で書く- フォームや Webhook の POST を保ちたい場合は 308 を検討する
- Let's Encrypt の認証パスは HTTP のまま応答させておくと更新が止まりにくい
- プロキシの背後では転送をプロキシ側で行うか
X-Forwarded-Protoで判定し、WordPress にも HTTPS を伝える - WordPress の URL 設定と本文中の URL を HTTPS に揃え、curl で転送回数まで確認する