2026年5月30日

2026年5月30日

NginxのHTTPSリダイレクトの推奨設定|転送1回・www統一・不正ホスト対策・HSTS

Nginx で http:// から https:// へ転送する基本の設定は、80番ポートの server ブロックに return 301 https://$host$request_uri; を書くことです(手順は「Nginx で HTTP から HTTPS にリダイレクトする設定」)。

この基本形のままでも転送はできますが、運用するサイトでは次の点も整えておくと、転送の回数が減り、思わぬホスト名で応答することも防げます。この記事の設定は、Nginx 1.24 と 1.30 で curl を使って動作を確かめました(2026年11月時点)。

推奨する設定の全体

www.example.com を正式な URL にする例です。www なしを正式にする場合は、www.example.com と example.com を入れ替えます。

# 1. 知らないホスト名への HTTP は応答せずに切る
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

# 2. 知らないホスト名への HTTPS は TLS の接続を断る(1.19.4 以降)
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;
}

# 3. HTTP: 正式な URL へ1回で転送する
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt の確認用ファイル(使う場合だけ。後述)
    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://www.example.com$request_uri;
    }
}

# 4. HTTPS の www なし: www ありへ転送する
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;

    return 301 https://www.example.com$request_uri;
}

# 5. HTTPS の正式な URL: サイトを表示する
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.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;
    # 以下、既存の設定
}

証明書は example.com と www.example.com の両方を含むもの(Certbot なら -d example.com -d www.example.com で取得したもの)を使います。以下、それぞれの理由を説明します。

転送は1回で済ませる

www の有無とHTTP/HTTPS の両方を直す必要があるとき、基本形のままだと、http://example.com/ は次のように2回転送されることがあります。

  1. http://example.com/ → https://example.com/(Nginx の HTTPS 転送)
  2. https://example.com/ → https://www.example.com/(WordPress などの www の統一)

上の設定の3番では、http://example.com/ から最終的な https://www.example.com/ へ直接転送します。転送が減る分、訪問者が表示されるまでの待ち時間が減ります。

WordPress の場合は、管理画面の「設定」→「一般」の「WordPress アドレス (URL)」と「サイトアドレス (URL)」を、Nginx の正式な URL(この例では https://www.example.com)と同じにします。違っていると、Nginx と WordPress が互いに逆向きへ転送し、ループします。

www の統一については「Nginx で www あり・なしを統一するリダイレクト」も参照してください。

$host ではなく固定のホスト名に転送する

$host は、リクエストの Host ヘッダーの値です。基本形の server ブロックがそのポートで最初(既定の server)になっていると、どんなホスト名で来たリクエストも受け付け、そのホスト名のまま転送します。実際に、基本形の設定で Host: evil.test を付けて HTTP でアクセスすると、Location: https://evil.test/ が返りました。

自分のサイトと関係ないホスト名で応答すると、別のドメインを自分のサーバーに向けられたときに、そのドメインのサイトとして振る舞ってしまいます。次の2つで防ぎます。

  • 転送先は https://www.example.com$request_uri のように固定のホスト名にする
  • default_server の server ブロックを別に作り、server_name に当てはまらないリクエストを断る

return 444 は Nginx 独自の値で、応答を返さずに接続を閉じます。HTTPS 側の ssl_reject_handshake on は、server_name に当てはまらない名前での TLS の接続を断ります(Nginx 1.19.4 以降。それより前のバージョンでは使えないので、その場合は2番の server ブロックを書かず、自分のサイトの server ブロックだけにします)。

Ubuntu・Debian のパッケージでは、/etc/nginx/sites-enabled/default にも listen 80 default_server; があります。default_server は同じアドレスとポートに1つしか書けず、nginx -t で「a duplicate default server」というエラーになるので、このリンクを外す(sudo rm /etc/nginx/sites-enabled/default。元のファイルは sites-available に残ります)か、そちらに1番の内容を書きます。

この設定にすると、IP アドレスを直接指定したアクセスにも応答しなくなります。IP アドレスでの表示が必要な場合は、その用途の server ブロックを別に用意します。

80番と443番は server ブロックを分ける

1つの server ブロックで 80番と443番の両方を listen し、if ($scheme = http) で分ける書き方もありますが、80番は転送だけ、443番は表示だけ、と分けたほうが設定が読みやすく、転送のループも起こしにくくなります。上の設定はこの考え方で書いています。

301 と 308 の使い分け

コード 意味 転送後のリクエスト
301 恒久的な移動 ブラウザが POST を GET に変えて送ることがある
308 恒久的な移動 POST は POST のまま、本文も含めて送る

ページの表示だけなら 301 で十分です。古いフォームや API の呼び出しが http:// に POST してくる可能性がある場合は、308 にすると送信内容が失われません。

location / {
    return 308 https://www.example.com$request_uri;
}

Google の検索セントラルのドキュメントでは、301 と 308 はどちらも恒久的な転送として扱われ、転送先が正式な URL だという合図になると説明されています。308 を転送として扱うのは Nginx 1.13.0 以降です。

Let’s Encrypt の更新と転送

Let's Encrypt の HTTP-01 の確認は、http:// の /.well-known/acme-challenge/ にアクセスします。Let's Encrypt の公式ドキュメントによると、この確認は転送を10回までたどり、https:// への転送先では証明書の検証をしません。そのため、確認用ファイルが HTTPS 側でも同じ場所から返るなら、すべてを転送したままでも更新できます。

HTTPS 側では確認用ファイルを返せない構成(HTTPS 側がリバースプロキシでほかのアプリにつながっているなど)では、上の設定の3番のように、80番の server ブロックで /.well-known/acme-challenge/ だけ転送せずにファイルを返します。その場合、Certbot は webroot 方式で、そのディレクトリを指定します。

sudo certbot certonly --webroot -w /var/www/letsencrypt -d example.com -d www.example.com

HSTS は動作を確かめてから入れる

HSTS(HTTP Strict Transport Security)は、「このサイトには今後 HTTPS でだけ接続する」とブラウザに覚えさせるヘッダーです。一度覚えたブラウザは、http:// のリンクでも最初から HTTPS で接続するため、最初の HTTP のリクエストが残りません。

正式な URL の HTTPS の server ブロック(上の5番)に書きます。

add_header Strict-Transport-Security "max-age=31536000" always;
  • always を付けると、エラーの応答にもヘッダーが付きます(付けないと 200 や 301 など一部の応答にだけ付きます)
  • 最初は max-age=300 のように短くし、問題が無いことを確かめてから伸ばします。ブラウザは max-age の間 HTTPS を強制するため、HTTPS をやめたくなっても、その期間は元に戻せません
  • includeSubDomains を付けると、すべてのサブドメインが対象になります。HTTPS に対応していないサブドメインがある場合は付けません
  • location の中に別の add_header があると、server ブロックの add_header はその location では使われません。その location にも同じ行を書きます(Nginx 1.29.3 以降なら add_header_inherit merge; で引き継げます)

HSTS の詳しい設定は「HSTS を設定して HTTPS を強制する」を参照してください。

CDN やロードバランサーを通している場合

Cloudflare やロードバランサーで HTTPS を終端し、そこから Nginx へ HTTP でつないでいる場合、Nginx の 80番ポートで HTTPS へ転送すると、ループします。この場合は、転送を CDN やロードバランサー側で行います(Cloudflare なら「Always Use HTTPS」)。Cloudflare の SSL/TLS の設定を「Full (strict)」にして、Cloudflare から Nginx へも HTTPS でつなぐ構成にすれば、この記事の設定をそのまま使えます。ループの調べ方は「リダイレクトが繰り返し行われましたの原因と直し方」で説明しています。

反映と確認

sudo nginx -t
sudo systemctl reload nginx

-L を付けた curl で、転送を最後までたどり、各段階のステータスと転送先を表示します。

for u in http://example.com/ http://www.example.com/ https://example.com/; do
  echo "== $u"
  curl -sIL "$u" | grep -iE '^HTTP|^location'
done

それぞれ、301 が1回だけ出て、Location が https://www.example.com/ になり、最後が 200 なら、意図どおりです。正式な URL では、HSTS のヘッダーも確かめます。

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

まとめ

  • 正式な URL(www の有無)を決め、HTTP からも www なしの HTTPS からも、正式な URL へ1回で転送する
  • 転送先は $host ではなく固定のホスト名にし、default_server で知らないホスト名を断る
  • 80番は転送、443番は表示と server ブロックを分ける
  • POST を保ちたいときは 308 を使う
  • Let's Encrypt の確認は転送をたどるが、HTTPS 側で返せない構成では /.well-known/acme-challenge/ を転送から外す
  • HSTS は動作を確かめてから、短い max-age で始める

お気軽にご相談ください

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