httpからhttpsにリダイレクトさせたいんです

私です。お母様と「httpからhttpsにリダイレクトさせたいのにどうするんだっけ？」を繰り返している皆々様、お元気ですか。
表題の件ですが、検索すると[`Rewrite`][mod-rewrite]よるURL書き換えばかりです。令和になってもこの調子で良いのか疑問に思ったので自分で調べることにしました。

# 忘れちゃイケナイHSTS

HTTP Strict Transport Security、通称HSTS。

ブラウザに「このサイトは`https`が実装してあるよ」と教えてあげるhttpヘッダー。強制力と信頼性は無いのでおまけのようなもの。
詳しいことはMDNの方で。

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Strict-Transport-Security

形式は次の通り。

```
Strict-Transport-Security: max-age=31536000
```

必須ディレクティブである`max-age`ではキャッシュ期間を指定する。`31536000`あれば良いかもしれない。

キャッシュ期間中はブラウザが自主的にリダイレクトを行う。

## なぜ `max-age=31536000` なのか？

**※これは私の解釈です**。

ドメイン、あるいはSSL/TLS証明書やサーバーの更新はほぼ年単位で行われ、HSTSのキャッシュ期間も`31536000 = 60 * 60 * 24 * 365`、つまり1年（年単位）にして間違いないだろう。

どうせブラウザ側の都合でキャッシュクリアされる可能性がある上、キャッシュ期間をこれより短くして得られるメリットがあるのかよく考えてほしい。

# 実装

主要なApache、nginx、LSWSに限定する。LSWSは都合上「[OpenLiteSpeed](https://openlitespeed.org/)」の事を指す。

前提として[Let’s Encrypt](https://letsencrypt.org/ja/)によるHTTP-01チャレンジ検証[^http-01-challenge]で失敗しないよう、`http://<YOUR_DOMAIN>/.well-known/acme-challenge/`にアクセスしても強制リダイレクトが発生しない仕組みで実装する。

## Apache 2.4+

Apache 2.4のコアモジュールには[`<If>`](https://httpd.apache.org/docs/2.4/en/mod/core.html#if)が追加実装されている。[mod_alias](https://httpd.apache.org/docs/2.4/en/mod/mod_alias.html)と組み合わせることにより、シンプルに記述できる。

```apache:"example.conf" or ".htaccess"
Header onsuccess set Strict-Transport-Security "max-age=31536000"
<If "%{HTTPS} != 'on' && %{REQUEST_URI} !~ m#^/\.well-known/acme-challenge/.*#">
# <If "%{HTTPS} != 'on'">
	Redirect permanent "https://%{HTTP_HOST}%{REQUEST_URI}"
</If>
```

HTTPステータスが200ならHSTSヘッダーを上書き追加する。

HTTP-01チャレンジを使用してないならコメントアウトしている`<If ...`と差し替える。

【必須モジュール】[mod_headers](https://httpd.apache.org/docs/2.4/en/mod/mod_headers.html) + mod_aliasが必要。どちらか欠けても`500 Internal Server Error`を返す。

## Apache 2.x
[mod_rewrite][mod-rewrite]を使った古典的なリダイレクト方式。

```apache:"example.conf" or ".htaccess"
Header onsuccess set Strict-Transport-Security "max-age=31536000"
RewriteEngine on
RewriteCond %{HTTPS} "!='on'"
RewriteCond %{REQUEST_URI} "!^/\.well-known/acme-challenge/.*"
RewriteRule .* "https://%{HTTP_HOST}/$0" [R=301]
```

上記で説明してるので端折るが、HTTP-01チャレンジ検証を利用してないなら4行目は削除して良い。

【必須モジュール】mod_headers + mod_rewriteが必要。どちらか欠けても`500 Internal Server Error`を返す。

## nginx

`return`による単純なリダイレクトで処理する。

```nginx:example.conf
location ~ "^/(?!\.well-known/acme-challenge/)" {
# location / {
	add_header Strict-Transport-Security "max-age=31536000";
	if ($https != “on”) {
		return 301 "https://$http_host$request_uri";
	}
}
```

【必須モジュール】[ngx_http_headers_module](http://nginx.org/en/docs/http/ngx_http_headers_module.html) + [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html)

## LiteSpeed Web Server

LSWSはApacheの`Rewrite`の設定内容をほぼそのまま使える。`RewriteRule`のパターンには、パッチ先頭に`/`を含むようで、一応修正する[^migrate-apache-rewrite-rules-to-openlitespeed]。

`Rewrite`の内容のみ`.htaccess`も利用可。どちらの方法でもリロード（再起動）は必要。

---

LiteSpeed WebAdmin コンソールから → バーチャルホスト → 対象ホストを編集 → コンテキスト より

- **URI**: `/`（重複不可）
- **アクセス可能**: `はい`

```:Header Operations
Strict-Transport-Security: max-age=31536000
```

- **Rewriteを有効にする**: `はい`

```apache:Rewrite ルール
RewriteCond %{HTTPS} "!='on'"
RewriteCond %{REQUEST_URI} "!^/\.well-known/acme-challenge/.*"
RewriteRule ^/?(.*)$ "https://%{HTTP_HOST}/$1" [R=301]
```

![lsws-context2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70146/00de29d3-08d4-d81b-b366-392e749fbc37.png)


あとはリロードを実行することで適用される。

----

一応設定ファイルの一部内容を置いておきます。可能ならウェブコンソールから設定を行って欲しい。

<details><summary>設定ファイルのサンプル内容</summary>

```:example.conf
context / {
  allowBrowse             1
  extraHeaders            <<<END_extraHeaders
Strict-Transport-Security: max-age=31536000
  END_extraHeaders
  rewrite  {
    enable                1
RewriteCond %{HTTPS} "!='on'"
RewriteCond %{REQUEST_URI} "!^/\.well-known/acme-challenge/.*"
RewriteRule ^/?(.*)$ "https://%{HTTP_HOST}/$1" [R=301]
  }
}
```

</details>

# 後記

本来`Rewrite`系はURLの置換によるリダイレクトである。当時は条件式系のディレクティブが発達しておらず仕方なく`Rewrite`を使う感じになっているがあまりにも定着しすぎている。Apache 2.4から`<If>` `<ElseIf>` `<Else>`ディレクティブの登場で単体で使う場面があまり少ないであろう`Redirect`も日の目を見ることができそうだ。

Apache 2.4は初版から10年経過しているので、置き換えの必要ないならシンプルなリダイレクトにするべき。

あとnginxの`if`がApacheほど使えない[^ifisevil]ので最終的に`location`に頼りがちになる。個人的に比較演算子の考え方はApacheの方が好みで、正規表現で扱う文字数はなるべく減らしたいので不一致があるのはとても助かる。

[mod-rewrite]: https://httpd.apache.org/docs/2.4/en/mod/mod_rewrite.html

[^http-01-challenge]: [チャレンジの種類](https://letsencrypt.org/ja/docs/challenge-types/#http-01-%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8)

[^ifisevil]: コアでは無く、[`ngx_http_rewrite_module`](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if)に実装されてるおまけのような機能。公式Wikiでも酷評されがち → [If is Evil… when used in location context](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/)

[^migrate-apache-rewrite-rules-to-openlitespeed]: [.htaccessとバーチャルホスト書き換えタブとバーチャルホストコンテキスト書き換えルールの書き換えルールの違い](https://openlitespeed.org/kb/migrate-apache-rewrite-rules-to-openlitespeed/)