PHP の $_SERVER['HTTPS'] が on になる流れ 〜Apache mod_php 編〜

PHP アプリケーションを実行するウェブサーバ（アプリケーションサーバ）があり、その前にロードバランサーを置く状況を考えます。今回は Apache 上の mod_php で PHP アプリケーションが実行されるとします。

この時、サイトは HTTPS だけど、実際の SSL 暗号化処理はロードバランサーのSSLアクセラレータ機能が行っており、ウェブサーバは HTTP 平文通信をしているというケースは良く見られます（昨今良く聞くのは AWS ELB の SSL リスナー機能）。

この時、PHP アプリケーション層で困ることがいくつかあります。たとえば以下。

* 処理サーバ自体は HTTP なので、ある場面で自サイトの別パスへリダイレクトを起こしたりする際に URL スキームが https ではなく http になったり、最悪アプリケーションの設計から無限リダイレクトが引き起こされる

この話は、WordPress 等の PHP アプリケーションで良く聞く話で、ネットを検索すると多くの話題が出てきます。

* [WordPressで、マルチサイト＋SSL環境でのリダイレクトループの対処は $_SERVER - H2O blog](https://blog.h2o-space.com/2016/10/1088/)
* [WordPressサイトのHTTPS(SSL)化でやったこと、やるべきこと - 樂印](https://rakuin.com/blog/https-for-wordpress/)
* [やればできる！Apacheリバースプロキシ+WordPress | hiro345](https://www.sssg.org/blogs/hiro345/archives/7013.html)
* [phpmyadminをSSLアクセラレータ環境下で使用する - うまいぼうぶろぐ](http://hogem.hatenablog.com/entry/20090316/1237149107)

これらのサイトの多くで書かれている結論は

* PHP アプリケーション層では、今稼働しているウェブサーバが HTTPS かどうかは `$_SERVER['HTTPS']` の値を見ている（`"on"` 等の偽ではない文字列が入っているか）
* なので、HTTPS サイトを運用する場合、SSLアクセラレータで復号して、Apache mod_php では平文 HTTP を受けている場合、混乱する場合がある
* WordPress で最初の方に呼ばれる編集して差し支えないファイルに `$_SERVER['HTTPS'] = 'on';` を追記すると、今自分（PHPアプリケーション）は末端のクライアントに HTTPS でコンテンツを提供していることが認識されて問題が起こらなくなる

特に[やればできる！Apacheリバースプロキシ+WordPress | hiro345](https://www.sssg.org/blogs/hiro345/archives/7013.html)には

> 一部の関数では `$_SERVER['HTTPS']` の値がどうなっているかによって、プロトコルを自動判定してしまうものがあるようなので、これも指定しています。

と書かれていて勉強になりました。

では `$_SERVER['HTTPS']` は誰が設定し、誰が見ているのでしょうか。検索してもこれに迫った記事が見つけられなかったので、自分なりにまとめてみました。

# PHP の `$_SERVER` 変数

公式マニュアルが詳しいです。

* [PHP: $_SERVER - Manual](http://php.net/manual/ja/reserved.variables.server.php)

> `$_SERVER` は、ヘッダ、パス、スクリプトの位置のような 情報を有する配列です。この配列のエントリは、Web サーバーにより 生成されます。全ての Web サーバーがこれら全てを提供する保障はありません。 サーバーは、これらのいくつかを省略したり、この一覧にない他のものを 定義する可能性があります。これらの変数の多くは、 [» CGI/1.1 specification](http://www.faqs.org/rfcs/rfc3875) で定義されています。したがって、これらについては定義されていることを 期待することができます。

要約すると

* `$_SERVER` は連想配列であり、そのキーの多くは CGI/1.1 の仕様で定義されている
* `$_SERVER` の情報はウェブサーバにより生成されるもの
* Web サーバによって、[このマニュアル](http://php.net/manual/ja/reserved.variables.server.php)にあるものを省く場合があるし、無いものを定義する可能性がある

といった感じ。要約の割には長いですね。

CGI/1.1 とだいたい同じということであれば、いわゆる環境変数がベースとなるのかな、それともさすがに今どき環境変数は無いかな、と若干混乱してしまいます。

# `$_SERVER['HTTPS']` について

[上述の公式マニュアル](http://php.net/manual/ja/reserved.variables.server.php)には

> 'HTTPS'
> スクリプトが HTTPS プロトコルを通じて実行されている場合に 空でない値が設定されます。
> 注意: ISAPI を IIS で使用している場合は、HTTPS プロトコルを通さないでリクエストが行われたときの値は off となることに注意しましょう。

と書かれています。誰が設定したかといった内容は書かれていません。

マニュアルにも言及があった [CGI/1.1 の仕様](http://www.faqs.org/rfcs/rfc3875.html) にも HTTPS という変数は書かれていませんでした。

10年以上前の Perl CGI 時代、慣習として `$ENV{HTTPS}` を見たことは覚えていますが、これは誰が設定しているのか、また `$_SERVER` と環境変数は同じなのか、調べていきます。

# Apache mod_php で `$_SERVER` を生成している部分

Apache の mod_php に限ると、 `$_SERVER` を生成している部分はどこなのでしょう。それは [PHP のソースコード](https://github.com/php/php-src) を見てみるのが早そうです。

[php-src/sapi/apache2handerl/sapi_apache2.c#L259](https://github.com/php/php-src/blob/9f456d68802f8a811be8f2487aa7778147711763/sapi/apache2handler/sapi_apache2.c#L259) によると、**環境変数を大体そのまま `$_SERVER` にコピーしている**挙動のようです。

Apache モジュールで `r->subprocess_env` は環境変数のキーバリュー構造のデータ（詳しくは [apr_tables.h](https://apr.apache.org/docs/apr/1.5/apr__tables_8h.html) を参照）を返します。

# HTTPS 環境変数を設定する部分

Apache だと mod_ssl を使うと HTTPS 環境変数が on になることは良く知られています。

であれば mod_ssl が環境変数 HTTPS を設定しているという推測になりますが、[httpd/modules/ssl/ssl_engine_kernel.c#L1384](https://github.com/apache/httpd/blob/95430471df720f8d884cc3ccb6c831ab22403a85/modules/ssl/ssl_engine_kernel.c#L1384) によると、実際そのようです。

この部分を見て学べるのは、Apache の[リクエスト処理フェーズ](https://httpd.apache.org/docs/2.4/developer/request.html)の中の fixups というフックで HTTPS 環境変数に "on" を設定しているという部分。fixups フックは、PHP の処理が走る handler フックの直前です。もしかしたらこれより前のフックで動作するもの、例えば VirtualHost コンテキストに書いた mod_rewrite の RewriteCond ディレクティブなどは mod_ssl が設定する HTTPS 環境変数を検出できないかもしれません（実験したわけではないので不正確かもしれません）。

# `$_SERVER['HTTPS']` しておきたい場合どうすればいいの？

Apache mod_php の場合、環境変数から構築されるのが `$_SERVER['HTTPS']` だということが上記で分かったので、サーバ設定の変更が手軽にできる場合は [SetEnv](http://httpd.apache.org/docs/2.0/ja/mod/mod_env.html) ディレクティブで定義するのが良いでしょう。

```apache
# SetEnv ディレクティブの使用のため mod_env が必要になります
SetEnv HTTPS on
```

**今までの解析結果から、上記 `SetEnv HTTPS on` 作戦が最もスマートな方法** と言えましょう。確かに `$_SERVER` が主に環境変数から生成されることが公式資料に明文化されていない（と思われる）ので、本当に正しいことか悩みだすとキリがありませんが、とはいえ **他者が開発したソフトウェアに手を入れずに解決できることは保守性（ソフトウェアのバージョンアップ等）や可搬性（他のサーバへの移設等）においてメリットが大きい** と思います。

上記のように Apache 設定ファイルを編集するのが素直ですが、事情があってサーバ設定ファイルを書き換えるのが難しいのであれば、使用している PHP アプリケーション全体で真っ先に読まれるファイルで `$_SERVER['HTTPS']` を書き換えるなどの方法を取る必要があるでしょう。追記が本当に差し支えないか（自動バージョンアップシステムやCIなど、何らかの機構でその PHP ファイルが意図せず頻繁に書き換えられたりしないかなど）は別途検証する必要があります。

```php
$_SERVER['HTTPS'] = 'on';
```